diff --git a/.env.example b/.env.example index faf6f07..402ca1a 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ PROJECT_NAME=jama # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.9.43 +JAMA_VERSION=0.9.44 # App port — the host port Docker maps to the container PORT=3000 diff --git a/backend/package.json b/backend/package.json index a6dbc17..606ce80 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.9.43", + "version": "0.9.44", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js index 94a649c..7a7603f 100644 --- a/backend/src/middleware/auth.js +++ b/backend/src/middleware/auth.js @@ -43,15 +43,18 @@ function adminMiddleware(req, res, next) { next(); } -// Allows admins OR members of groups designated as Group Managers or Schedule Managers +// Allows admins OR members of groups designated as Tool Managers function teamManagerMiddleware(req, res, next) { if (req.user?.role === 'admin') return next(); const db = getDb(); - const gmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_group_managers'").get(); - const smSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_schedule_managers'").get(); + // Prefer unified key, fall back to legacy keys for older installs + const tmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_tool_managers'").get(); + const gmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_group_managers'").get(); const allowedGroupIds = [ - ...JSON.parse(gmSetting?.value || '[]'), - ...JSON.parse(smSetting?.value || '[]'), + ...new Set([ + ...JSON.parse(tmSetting?.value || '[]'), + ...JSON.parse(gmSetting?.value || '[]'), + ]) ]; if (allowedGroupIds.length === 0) return res.status(403).json({ error: 'Access denied' }); const member = db.prepare(` diff --git a/backend/src/models/db.js b/backend/src/models/db.js index 1421885..d837965 100644 --- a/backend/src/models/db.js +++ b/backend/src/models/db.js @@ -220,6 +220,7 @@ function initDb() { insertSetting.run('app_type', 'JAMA-Chat'); insertSetting.run('team_group_managers', ''); insertSetting.run('team_schedule_managers', ''); + insertSetting.run('team_tool_managers', ''); // Migration: add hide_admin_tag if upgrading from older version try { diff --git a/backend/src/routes/settings.js b/backend/src/routes/settings.js index 66909c0..7dfd115 100644 --- a/backend/src/routes/settings.js +++ b/backend/src/routes/settings.js @@ -174,11 +174,16 @@ router.post('/register', authMiddleware, adminMiddleware, (req, res) => { // Save team management group assignments router.patch('/team', authMiddleware, adminMiddleware, (req, res) => { - const { groupManagers, scheduleManagers } = req.body; + const { toolManagers } = req.body; const db = getDb(); const upd = db.prepare("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')"); - if (groupManagers !== undefined) upd.run('team_group_managers', JSON.stringify(groupManagers || []), JSON.stringify(groupManagers || [])); - if (scheduleManagers !== undefined) upd.run('team_schedule_managers', JSON.stringify(scheduleManagers || []), JSON.stringify(scheduleManagers || [])); + if (toolManagers !== undefined) { + const val = JSON.stringify(toolManagers || []); + upd.run('team_tool_managers', val, val); + // Keep legacy keys in sync so existing teamManagerMiddleware still works + upd.run('team_group_managers', val, val); + upd.run('team_schedule_managers', val, val); + } res.json({ success: true }); }); diff --git a/build.sh b/build.sh index 839b749..47e4f84 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.9.43}" +VERSION="${1:-0.9.44}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index abc38f1..8b80c4f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.9.43", + "version": "0.9.44", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/NavDrawer.jsx b/frontend/src/components/NavDrawer.jsx index dd72fad..46f2599 100644 --- a/frontend/src/components/NavDrawer.jsx +++ b/frontend/src/components/NavDrawer.jsx @@ -17,11 +17,9 @@ export default function NavDrawer({ open, onClose, onMessages, onGroupManager, o const isAdmin = user?.role === 'admin'; const isMobile = window.matchMedia('(pointer: coarse)').matches || window.innerWidth < 768; - // Team-managed access: check if user is in any of the designated manager groups - // (frontend-only — no API enforcement yet) + // Tool Manager access: admin always passes; non-admins pass if in a designated tool manager group const userGroupIds = features.userGroupMemberships || []; - const canAccessGroupManager = isAdmin || (features.teamGroupManagers || []).some(gid => userGroupIds.includes(gid)); - const canAccessScheduleManager = isAdmin || (features.teamScheduleManagers || []).some(gid => userGroupIds.includes(gid)); + const canAccessTools = isAdmin || (features.teamToolManagers || []).some(gid => userGroupIds.includes(gid)); // Close on outside click useEffect(() => { @@ -74,22 +72,22 @@ export default function NavDrawer({ open, onClose, onMessages, onGroupManager, o {item(NAV_ICON.messages, 'Messages', onMessages)} {item(NAV_ICON.schedules, 'Schedules', () => {}, true)} - {/* Admin-only tools */} + {/* Admin-only: Branding + Settings */} {isAdmin && ( <>
{description}
+ return ( ++ Members of selected groups can access Group Manager, Schedule Manager, and User Manager. Admin users always have access to all three tools. +
{userGroups.length === 0 ? ( -No user groups created yet. Create groups in the Group Manager first.
+No user groups created yet. Create groups in the Group Manager first.
) : ( -No groups selected — admins only.
+ {toolManagers.length === 0 && ( +No groups selected — tools are admin-only.
)} -