From acfd2c7b1481e2da7a3c66a18a3aed18c68cd061 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:09:08 +0100 Subject: [PATCH] Roles and permissions view completed --- backend/api/permissions/resource_actions.go | 7 +- backend/api/roles/add_permission.go | 13 +- backend/api/roles/remove_permission.go | 18 +- backend/api/roles_routes.go | 4 +- .../(auth)/dashboard/settings/roles/page.tsx | 176 ++++++++++-------- 5 files changed, 119 insertions(+), 99 deletions(-) diff --git a/backend/api/permissions/resource_actions.go b/backend/api/permissions/resource_actions.go index 52d6907..cd7f614 100644 --- a/backend/api/permissions/resource_actions.go +++ b/backend/api/permissions/resource_actions.go @@ -34,17 +34,14 @@ func HandleResourceActions(w http.ResponseWriter, r *http.Request) { return } - result := make([]map[string]interface{}, 0) + result := make(map[string]interface{}, 0) for _, gp := range groupedPermissions { var actions []string _ = gp.Actions.AssignTo(&actions) - result = append(result, map[string]interface{}{ - "resource": gp.Resource, - "actions": actions, - }) + result[gp.Resource] = actions } core.JSONSuccess{ diff --git a/backend/api/roles/add_permission.go b/backend/api/roles/add_permission.go index 8fbb8ba..f951dd4 100644 --- a/backend/api/roles/add_permission.go +++ b/backend/api/roles/add_permission.go @@ -3,20 +3,19 @@ package roles import ( "context" "net/http" - "strconv" "fr.latosa-escrima/core" "fr.latosa-escrima/core/models" - "github.com/google/uuid" ) func HandleAddPermission(w http.ResponseWriter, r *http.Request) { ctx := context.Background() role_id := r.PathValue("role_uuid") - permission_id := r.PathValue("permission_id") + resource := r.PathValue("resource") + action := r.PathValue("action") var permission models.Permission count, err := core.DB.NewSelect().Model(&permission). - Where("id = ?", permission_id). + Where("resource = ? AND action = ?", resource, action). Limit(1).ScanAndCount(ctx) if count == 0 { core.JSONError{ @@ -39,11 +38,9 @@ func HandleAddPermission(w http.ResponseWriter, r *http.Request) { return } - pid, err := strconv.Atoi(permission_id) - rid, err := uuid.Parse(role_id) permissionRole := models.PermissionToRole{ - PermissionID: pid, - RoleID: rid, + PermissionID: permission.ID, + RoleID: role.ID, } _, err = core.DB.NewInsert().Model(&permissionRole).Ignore(). Exec(ctx) diff --git a/backend/api/roles/remove_permission.go b/backend/api/roles/remove_permission.go index 448c50b..8538164 100644 --- a/backend/api/roles/remove_permission.go +++ b/backend/api/roles/remove_permission.go @@ -11,10 +11,22 @@ import ( func HandleRemovePermission(w http.ResponseWriter, r *http.Request) { ctx := context.Background() role_id := r.PathValue("role_uuid") - permission_id := r.PathValue("permission_id") + resource := r.PathValue("resource") + action := r.PathValue("action") + var permission models.Permission + count, err := core.DB.NewSelect().Model(&permission). + Where("resource = ? AND action = ?", resource, action). + Limit(1).ScanAndCount(ctx) + if count == 0 { + core.JSONError{ + Status: core.Error, + Message: "Permission doesn't exist.", + }.Respond(w, http.StatusNotFound) + return + } - _, err := core.DB.NewDelete().Model((*models.PermissionToRole)(nil)). - Where("permission_id = ? AND role_id = ?", permission_id, role_id). + _, err = core.DB.NewDelete().Model((*models.PermissionToRole)(nil)). + Where("permission_id = ? AND role_id = ?", permission.ID, role_id). Exec(ctx) if err != nil { diff --git a/backend/api/roles_routes.go b/backend/api/roles_routes.go index d8e08ab..9fc844f 100644 --- a/backend/api/roles_routes.go +++ b/backend/api/roles_routes.go @@ -30,11 +30,11 @@ var RolesRoutes = map[string]core.Handler{ Handler: roles.HandleRolePermissions, Middlewares: []core.Middleware{Methods("GET"), AuthJWT}, }, - "/roles/{role_uuid}/permissions/{permission_id}/add": { + "/roles/{role_uuid}/permissions/{resource}/{action}/add": { Handler: roles.HandleAddPermission, Middlewares: []core.Middleware{Methods("PATCH"), AuthJWT}, }, - "/roles/{role_uuid}/permissions/{permission_id}/remove": { + "/roles/{role_uuid}/permissions/{resource}/{action}/remove": { Handler: roles.HandleRemovePermission, Middlewares: []core.Middleware{Methods("PATCH"), AuthJWT}, }, diff --git a/frontend/app/(auth)/dashboard/settings/roles/page.tsx b/frontend/app/(auth)/dashboard/settings/roles/page.tsx index 6130604..47bf858 100644 --- a/frontend/app/(auth)/dashboard/settings/roles/page.tsx +++ b/frontend/app/(auth)/dashboard/settings/roles/page.tsx @@ -15,84 +15,62 @@ import { } from "@/components/ui/dialog"; import { ChevronDown, ChevronRight, Plus, Trash2 } from "lucide-react"; import { toTitleCase } from "@/lib/utils"; -import { useApi } from "@/hooks/use-api"; +import { request, useApi } from "@/hooks/use-api"; -type Action = "create" | "read" | "update" | "delete"; +type Action = string; interface Permission { resource: string; - actions: Action[]; + action: Action; } interface Role { + id: string; name: string; - permissions: Permission[]; + permissions?: Permission[]; } -// Sample data -const initialRoles: Role[] = [ - { - name: "Admin", - permissions: [ - { - resource: "users", - actions: ["create", "read", "update", "delete"], - }, - { - resource: "events", - actions: ["create", "read", "update", "delete"], - }, - { - resource: "blogs", - actions: ["create", "read", "update", "delete"], - }, - ], - }, - { - name: "Editor", - permissions: [ - { resource: "users", actions: ["read"] }, - { resource: "events", actions: ["create", "read", "update"] }, - { resource: "blogs", actions: ["create", "read", "update"] }, - ], - }, -]; - interface PermissionsGrouped { - resource: string; - actions: string[]; + [key: string]: string[]; } export default function RolesAndPermissions() { - const [roles, setRoles] = useState(initialRoles); const [newRoleName, setNewRoleName] = useState(""); const [isDialogOpen, setIsDialogOpen] = useState(false); - const { data: permissions } = useApi( + const { data: permissions } = useApi( "/permissions/grouped", {}, true, ); - const addNewRole = () => { + const { data: roles, mutate: rolesMutate } = useApi( + "/roles", + {}, + true, + ); + + const addNewRole = async () => { if (newRoleName.trim() === "") return; - const newRole: Role = { - name: newRoleName.trim(), - permissions: [ - { resource: "users", actions: [] }, - { resource: "events", actions: [] }, - { resource: "blogs", actions: [] }, - ], - }; - setRoles([...roles, newRole]); + const res = await request("/roles/new", { + requiresAuth: true, + method: "POST", + body: { name: newRoleName }, + }); + + if (res.status === "Success") rolesMutate(); + setNewRoleName(""); setIsDialogOpen(false); }; - const deleteRole = (index: number) => { - const updatedRoles = roles.filter((_, i) => i !== index); - setRoles(updatedRoles); + const deleteRole = async (id: string) => { + const res = await request(`/roles/${id}/delete`, { + method: "DELETE", + requiresAuth: true, + }); + if (res.status === "Success") rolesMutate(); }; return ( @@ -127,13 +105,16 @@ export default function RolesAndPermissions() { - {roles.map((role, index) => ( - deleteRole(index)} - /> - ))} + {permissions && + roles && + roles.map((role, index) => ( + deleteRole(role.id)} + /> + ))} ); } @@ -141,25 +122,30 @@ export default function RolesAndPermissions() { interface RoleCardProps { role: Role; onDelete: () => void; + permissions: PermissionsGrouped; } -function RoleCard({ role, onDelete }: RoleCardProps) { +function RoleCard({ role, onDelete, permissions }: RoleCardProps) { return ( - {role.name} + {toTitleCase(role.name)} - {role.permissions.map((permission) => ( - - ))} + {Object.entries(permissions).map(([res, actions]) => { + console.log(role.permissions); + return ( + + ); + })} ); @@ -167,12 +153,48 @@ function RoleCard({ role, onDelete }: RoleCardProps) { interface ResourceSectionProps { resource: string; - actions: Action[]; + defaultActions: string[]; + role: Role; } -function ResourceSection({ resource, actions }: ResourceSectionProps) { +function ResourceSection({ + resource, + defaultActions, + role, +}: ResourceSectionProps) { const [isExpanded, setIsExpanded] = useState(false); + const a = (role.permissions ?? []) + .map((p) => (p.resource === resource ? p.action : null)) + .filter((a) => a !== null); + + const ActionCheckbox = ({ action }: { action: Action }) => { + const [checked, setChecked] = useState(a.includes(action)); + return ( +
+ { + if (typeof e === "boolean") { + const res = await request( + `/roles/${role.id}/permissions/${resource}/${action}/${e ? "add" : "remove"}`, + { method: "PATCH", requiresAuth: true }, + ); + if (res.status === "Success") setChecked(e); + } + }} + checked={checked} + id={`${resource}-${action}`} + /> + +
+ ); + }; + return (