From 79caa1ecb340f3c9f18621631948121b0bb53ef2 Mon Sep 17 00:00:00 2001 From: biersoeckli Date: Thu, 6 Mar 2025 17:43:44 +0000 Subject: [PATCH] feat: add app permissions management to role editing and enhance roles table with app data --- src/app/backups/backups-table.tsx | 4 - src/app/settings/users/page.tsx | 4 +- src/app/settings/users/role-edit-overlay.tsx | 120 +++++++++++++++++-- src/app/settings/users/roles-table.tsx | 8 +- src/server/services/app.service.ts | 8 ++ src/shared/model/role-edit.model.ts | 4 + 6 files changed, 129 insertions(+), 19 deletions(-) diff --git a/src/app/backups/backups-table.tsx b/src/app/backups/backups-table.tsx index ebcd782..6e80a40 100644 --- a/src/app/backups/backups-table.tsx +++ b/src/app/backups/backups-table.tsx @@ -1,18 +1,14 @@ 'use client' import { Button } from "@/components/ui/button"; - import { SimpleDataTable } from "@/components/custom/simple-data-table"; import { formatDateTime } from "@/frontend/utils/format.utils"; import { List } from "lucide-react"; import { BackupInfoModel } from "@/shared/model/backup-info.model"; import { BackupDetailDialog } from "./backup-detail-overlay"; - - export default function BackupsTable({ data }: { data: BackupInfoModel[] }) { - return <> - + diff --git a/src/app/settings/users/role-edit-overlay.tsx b/src/app/settings/users/role-edit-overlay.tsx index ee9741d..766f78b 100644 --- a/src/app/settings/users/role-edit-overlay.tsx +++ b/src/app/settings/users/role-edit-overlay.tsx @@ -22,15 +22,23 @@ import { ScrollArea } from "@/components/ui/scroll-area" import { saveRole } from "./actions" import { RoleExtended } from "@/shared/model/role-extended.model.ts" import { RoleEditModel, roleEditZodModel } from "@/shared/model/role-edit.model" +import { App } from "@prisma/client" +import { Checkbox } from "@/components/ui/checkbox" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" - -export default function RoleEditOverlay({ children, role }: { +export default function RoleEditOverlay({ children, role, apps }: { children: React.ReactNode; role?: RoleExtended; + apps: App[] }) { const [isOpen, setIsOpen] = useState(false); - + const [appPermissions, setAppPermissions] = useState<{ + appId: string; + read: boolean; + readwrite: boolean; + }[]>([]); + const form = useForm({ resolver: zodResolver(roleEditZodModel), defaultValues: role @@ -40,7 +48,14 @@ export default function RoleEditOverlay({ children, role }: { payload: RoleEditModel) => saveRole(state, { ...payload, - id: role?.id + id: role?.id, + roleAppPermissions: appPermissions.flatMap(perm => { + if (!perm.read && !perm.readwrite) return []; + return [{ + appId: perm.appId, + permission: perm.readwrite ? 'READWRITE' : 'READ' + }]; + }) }), FormUtils.getInitialFormState()); useEffect(() => { @@ -55,21 +70,70 @@ export default function RoleEditOverlay({ children, role }: { useEffect(() => { if (role) { form.reset(role); + + // Initialize app permissions based on role data + const initialPermissions = apps.map(app => { + const existingPermission = role.roleAppPermissions?.find(p => p.appId === app.id); + return { + appId: app.id, + read: !!existingPermission && existingPermission.permission === 'READ', + readwrite: !!existingPermission && existingPermission.permission === 'READWRITE' + }; + }); + + setAppPermissions(initialPermissions); + } else { + // Initialize with all apps having no permissions + const initialPermissions = apps.map(app => ({ + appId: app.id, + read: false, + readwrite: false + })); + + setAppPermissions(initialPermissions); } - }, [role]); + }, [role, apps]); + + const handleReadChange = (appId: string, checked: boolean) => { + setAppPermissions(prev => prev.map(perm => { + if (perm.appId === appId) { + // If read is being turned off, also turn off readwrite + if (!checked) { + return { ...perm, read: false, readwrite: false }; + } + // If read is being turned on, just update read + return { ...perm, read: checked }; + } + return perm; + })); + }; + + const handleReadWriteChange = (appId: string, checked: boolean) => { + setAppPermissions(prev => prev.map(perm => { + if (perm.appId === appId) { + // If readwrite is being turned on, also turn on read + if (checked) { + return { ...perm, read: true, readwrite: true }; + } + // If readwrite is being turned off, just update readwrite + return { ...perm, readwrite: false }; + } + return perm; + })); + }; return ( <>
setIsOpen(true)}> {children}
- setIsOpen(false)}> - + setIsOpen(isOpened)}> + {role?.id ? 'Edit' : 'Create'} Role -
+
form.handleSubmit((data) => { return formAction(data); @@ -89,6 +153,43 @@ export default function RoleEditOverlay({ children, role }: { )} /> +
+

App Permissions

+ + + + App + Read + ReadWrite + + + + {apps.map((app) => { + const permission = appPermissions.find(p => p.appId === app.id); + return ( + + {app.name} + + handleReadChange(app.id, !!checked)} + /> + + + handleReadWriteChange(app.id, !!checked)} + /> + + + ); + })} + +
+

{state.message}

Save @@ -101,7 +202,4 @@ export default function RoleEditOverlay({ children, role }: {
) - - - } \ No newline at end of file diff --git a/src/app/settings/users/roles-table.tsx b/src/app/settings/users/roles-table.tsx index e02d4f6..9757914 100644 --- a/src/app/settings/users/roles-table.tsx +++ b/src/app/settings/users/roles-table.tsx @@ -10,9 +10,11 @@ import { formatDateTime } from "@/frontend/utils/format.utils"; import { deleteRole } from "./actions"; import { RoleExtended } from "@/shared/model/role-extended.model.ts"; import RoleEditOverlay from "./role-edit-overlay"; +import { App } from "@prisma/client"; -export default function RolesTable({ roles }: { +export default function RolesTable({ roles, apps }: { roles: RoleExtended[]; + apps: App[]; }) { const { openConfirmDialog: openDialog } = useConfirmDialog(); @@ -42,7 +44,7 @@ export default function RolesTable({ roles }: { <>
- +
} /> - + ; diff --git a/src/server/services/app.service.ts b/src/server/services/app.service.ts index 6b102f1..b07071a 100644 --- a/src/server/services/app.service.ts +++ b/src/server/services/app.service.ts @@ -468,6 +468,14 @@ class AppService { revalidateTag(Tags.apps(existingItem.app.projectId)); } } + + async getAll() { + return await dataAccess.client.app.findMany({ + orderBy: { + name: 'asc' + } + }); + } } const appService = new AppService(); diff --git a/src/shared/model/role-edit.model.ts b/src/shared/model/role-edit.model.ts index fa55006..ec4f664 100644 --- a/src/shared/model/role-edit.model.ts +++ b/src/shared/model/role-edit.model.ts @@ -4,6 +4,10 @@ import { z } from "zod"; export const roleEditZodModel = z.object({ id: z.string().trim().optional(), name: z.string().trim().min(1), + roleAppPermissions: z.array(z.object({ + appId: z.string(), + permission: z.string(), + })).optional(), }) export type RoleEditModel = z.infer;