Task #74: Add groups system + admin User Management UI
Backend: - New schema: groups, user_groups, group_apps, group_roles (lib/db/src/schema/groups.ts) - Seeds Admins, TeaBoy, Everyone system groups idempotently and maps existing users - /api/groups CRUD with admin guard, batch counts, system-group delete protection - Validates appIds/roleIds/userIds (400 on missing) and wraps assignment writes in a single DB transaction (no partial state on failure) - /api/users gains q/groupId/status filters, batch role+group loading, groupIds replacement on PATCH, auto-assigns Everyone on admin-create - /auth/register also auto-assigns Everyone for consistent default linkage - buildAuthUser now returns groups (matches updated AuthUser OpenAPI schema) - App visibility (getVisibleAppsForUser) unions group-granted apps via group_apps + user_groups in addition to existing permission gating Frontend (admin.tsx): - Nav restructured: User Management section with Users + Groups children - Section deep-linked via #section=… URL hash - Users page rebuilt: search, group filter, status filter, sortable table, groups column, edit-groups dialog, mobile cards - New Groups page: cards with member/app/role counts, create dialog, detail editor with Info/Apps/Users tabs and system-group guard - ar/en translations added for all new keys Testing: - pnpm typecheck clean (api + web) - 25/26 api tests pass; the only failure is pre-existing flaky pagination test (admin-app-opens-pagination) — left as-is per scratchpad note - Code review feedback addressed (validation, transactions, register auto-assign)
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
||||
rolesTable,
|
||||
userAppOrdersTable,
|
||||
appOpensTable,
|
||||
userGroupsTable,
|
||||
groupAppsTable,
|
||||
} from "@workspace/db";
|
||||
import { requireAuth, requireAdmin } from "../middlewares/auth";
|
||||
import {
|
||||
@@ -76,9 +78,26 @@ async function getVisibleAppsForUser(userId: number): Promise<typeof appsTable.$
|
||||
).map((r) => r.appId)
|
||||
: [];
|
||||
|
||||
// Group-granted apps: any app explicitly assigned to one of the user's groups
|
||||
// is visible regardless of the legacy permission gating.
|
||||
const groupGrantedAppIds = (
|
||||
await db
|
||||
.select({ appId: groupAppsTable.appId })
|
||||
.from(groupAppsTable)
|
||||
.innerJoin(
|
||||
userGroupsTable,
|
||||
eq(userGroupsTable.groupId, groupAppsTable.groupId),
|
||||
)
|
||||
.where(eq(userGroupsTable.userId, userId))
|
||||
).map((r) => r.appId);
|
||||
const groupGrantedSet = new Set(groupGrantedAppIds);
|
||||
|
||||
const allowedAppIdSet = new Set(allowedRestrictedAppIds);
|
||||
return apps.filter(
|
||||
(app) => !restrictedAppIds.has(app.id) || allowedAppIdSet.has(app.id),
|
||||
(app) =>
|
||||
!restrictedAppIds.has(app.id) ||
|
||||
allowedAppIdSet.has(app.id) ||
|
||||
groupGrantedSet.has(app.id),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user