From 8ab9a9a497a5302171483ea37a102691d55d013b Mon Sep 17 00:00:00 2001 From: Syahdan Date: Mon, 29 Jun 2026 21:10:22 +0700 Subject: [PATCH] feat: add role-based access control --- apps/web/src/components/dashboard-shell.tsx | 45 ++++-- apps/web/src/routeTree.gen.ts | 21 +++ apps/web/src/routes/dashboard.tsx | 9 +- apps/web/src/routes/dashboard.users.tsx | 149 ++++++++++++++++++ packages/api/src/context.ts | 25 +++ packages/api/src/index.ts | 31 ++++ packages/api/src/permissions.ts | 1 + packages/api/src/routers/dashboard.ts | 4 +- packages/api/src/routers/domains.ts | 16 +- packages/api/src/routers/index.ts | 2 + packages/api/src/routers/notes.ts | 14 +- packages/api/src/routers/schemas.ts | 5 + packages/api/src/routers/servers.ts | 12 +- packages/api/src/routers/services.ts | 12 +- packages/api/src/routers/users.ts | 90 +++++++++++ packages/db/src/migrations/0001_add_rbac.sql | 40 +++++ packages/db/src/migrations/meta/_journal.json | 9 +- packages/db/src/rbac.ts | 47 ++++++ packages/db/src/schema/auth.ts | 92 ++++++++++- packages/db/src/seed-if-empty.ts | 5 +- packages/db/src/seed.ts | 46 +++++- 21 files changed, 629 insertions(+), 46 deletions(-) create mode 100644 apps/web/src/routes/dashboard.users.tsx create mode 100644 packages/api/src/permissions.ts create mode 100644 packages/api/src/routers/users.ts create mode 100644 packages/db/src/migrations/0001_add_rbac.sql create mode 100644 packages/db/src/rbac.ts diff --git a/apps/web/src/components/dashboard-shell.tsx b/apps/web/src/components/dashboard-shell.tsx index a0147e4..a3ddfee 100644 --- a/apps/web/src/components/dashboard-shell.tsx +++ b/apps/web/src/components/dashboard-shell.tsx @@ -15,6 +15,7 @@ import { LayoutGrid, MenuIcon, Server, + Users, Wrench, } from "lucide-react"; import { useEffect, useState } from "react"; @@ -25,30 +26,42 @@ const dashboardLinks = [ label: "Overview", description: "Health, alerts, and recent checks", icon: LayoutGrid, + permission: "dashboard.read", }, { to: "/dashboard/servers", label: "Servers", description: "Inventory and host details", icon: Server, + permission: "servers.read", }, { to: "/dashboard/services", label: "Services", description: "Processes and uptime targets", icon: Wrench, + permission: "services.read", }, { to: "/dashboard/domains", label: "Domains", description: "DNS, SSL, and expiry status", icon: Globe, + permission: "domains.read", }, { to: "/dashboard/notes", label: "Notes", description: "Runbooks and operational handover", icon: BookText, + permission: "notes.read", + }, + { + to: "/dashboard/users", + label: "Users", + description: "Roles and permission assignments", + icon: Users, + permission: "users.read", }, ] as const; @@ -60,14 +73,18 @@ function isActive(pathname: string, to: string): boolean { function NavItems({ pathname, + permissions, onNavigate, }: { pathname: string; + permissions: string[]; onNavigate?: () => void; }) { + const allowedPermissions = new Set(permissions); + return (