Option A — Simple Roles Management
Status: [To Build]
Effort: ~1 day
Risk: Low — no auth check changes, no breaking schema changes
Overview
Add a Role table for display metadata only. The four platform roles and three tenant roles remain hardcoded in code (no runtime dispatch change). Superadmins can view all roles, edit their label/description/color, and see how many users hold each role. Custom roles are not supported in this option.
Database Schema
model Role {
id String @id @default(cuid())
identifier String @unique // e.g. "super_admin", "admin"
label String // e.g. "Super Admin"
description String @default("")
color String @default("gray") // tailwind color name: violet, blue, amber, gray
level String // "platform" | "tenant"
isSystem Boolean @default(true) // system roles cannot be deleted
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Seed the 4 platform + 3 tenant roles on first migration.
API Routes
All under GET|POST|PATCH|DELETE /admin/v1/roles — superadmin only.
| Method | Path | Description |
|---|---|---|
GET | /admin/v1/roles | List all roles with user counts |
GET | /admin/v1/roles/:roleId | Role detail + users who have this role |
POST | /admin/v1/roles | Create role (isSystem: false) |
PATCH | /admin/v1/roles/:roleId | Edit label, description, color |
DELETE | /admin/v1/roles/:roleId | Delete role (blocked if isSystem: true) |
User count per role is a GROUP BY on User.role (platform) and TenantMember.role (tenant).
Screens
RL1 — Roles List
Route: /roles
Access: Superadmin only
Layout:
- Page header: “Roles” + “Add Role” button (top right)
- Two sections: Platform Roles and Tenant Roles (h3 separators)
- Card grid (not table) — one card per role
Role card contains:
- Color dot + label (e.g. “Super Admin”)
- Identifier badge (e.g.
super_admin) — monospace, muted - Description (truncated to 2 lines)
- User count chip (e.g. “3 users”)
Systembadge ifisSystem: true- Edit icon button → RL3
- Delete icon button → disabled + tooltip “System role cannot be deleted” if
isSystem: true
Empty state: Not applicable (system roles always seeded).
RL2 — Role Detail
Route: /roles/[roleId]
Access: Superadmin only
Layout (two-column):
Left column:
- Role label (h1) + identifier badge + System badge (if applicable)
- Description (full text)
- Level chip: “Platform role” or “Tenant role”
- Color swatch
- Created / Updated timestamps
- Edit button → RL3
Right column:
- “Users with this role” table
- Platform roles: list
Userrecords whererole = identifier; columns: Name, Email, Joined - Tenant roles: list
TenantMemberrecords whererole = identifier; columns: Name, Email, Tenant, Joined - Paginated (20 per page)
- Empty state: “No users have this role yet.”
- Platform roles: list
RL3 — Add / Edit Role
Route: /roles/new and /roles/[roleId]/edit
Access: Superadmin only
Form fields:
| Field | Type | Notes |
|---|---|---|
| Label | Text input | Required; max 50 chars |
| Identifier | Text input | Required on create; slug format ([a-z_]+); readonly on edit for system roles |
| Description | Textarea | Optional; max 300 chars |
| Level | Select | platform or tenant; readonly on edit for system roles |
| Color | Color picker | Tailwind palette: gray, violet, blue, amber, green, red, orange |
Constraints:
- System roles (
isSystem: true): label, description, color are editable; identifier and level are readonly - Identifier must be unique — validate on blur (live API check)
Actions: Save (primary) | Cancel (secondary)
Sidebar Nav Entry
Add to the System group in apps/manage/src/components/manage-sidebar.tsx:
System
├── RAG & AI Config (existing)
└── Roles (new) → /rolesIcon: LuShieldCheck (lucide-react)
Audit Logging
Use the existing writeAuditLog helper for all mutations:
| Event | Action |
|---|---|
| Create role | role.created |
| Edit role | role.updated |
| Delete role | role.deleted |