Skip to Content
RolesOption A — Simple Roles Management

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.

MethodPathDescription
GET/admin/v1/rolesList all roles with user counts
GET/admin/v1/roles/:roleIdRole detail + users who have this role
POST/admin/v1/rolesCreate role (isSystem: false)
PATCH/admin/v1/roles/:roleIdEdit label, description, color
DELETE/admin/v1/roles/:roleIdDelete 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”)
  • System badge if isSystem: 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 User records where role = identifier; columns: Name, Email, Joined
    • Tenant roles: list TenantMember records where role = identifier; columns: Name, Email, Tenant, Joined
    • Paginated (20 per page)
    • Empty state: “No users have this role yet.”

RL3 — Add / Edit Role

Route: /roles/new and /roles/[roleId]/edit
Access: Superadmin only

Form fields:

FieldTypeNotes
LabelText inputRequired; max 50 chars
IdentifierText inputRequired on create; slug format ([a-z_]+); readonly on edit for system roles
DescriptionTextareaOptional; max 300 chars
LevelSelectplatform or tenant; readonly on edit for system roles
ColorColor pickerTailwind 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) → /roles

Icon: LuShieldCheck (lucide-react)


Audit Logging

Use the existing writeAuditLog helper for all mutations:

EventAction
Create rolerole.created
Edit rolerole.updated
Delete rolerole.deleted

© 2026 Leadmetrics — Internal use only