zazzy
Features

Admin panel

Internal back-office — user/org search, impersonation, and overrides.

The admin panel is an internal, platform-scoped back-office at /admin. Unlike every other surface it is not org-scoped: a platform admin searches across all users and orgs, impersonates a user, and applies plan and flag overrides through the billing and flags seams. Access is gated by a dedicated admin procedure and bootstrapped with a CLI.

Optional feature

Admin is optional (core: false, requires: ["orgs", "billing", "flags"]). Its override writes go through billing and flags rather than touching their data directly, and its usage/audit panels are soft seams.

What it does

  • Platform-admin gateadminProcedure (server/admin-procedure.ts) layers on protectedProcedure: unauthenticated → UNAUTHORIZED, signed-in non-staff → FORBIDDEN. Role comes from ctx.user.role; platformRoleSatisfies(role, min) and PLATFORM_ROLE_ORDER leave room for a future support tier.
  • Search tablessearchUsers (offset-paginated over the Better Auth admin plugin) and searchOrgs (cursor-paginated over Prisma), surfaced as AdminUsersTable, AdminOrgsTable, and AdminDashboard (platform counts + recent signups).
  • Org detailadmin.orgDetail feeds AdminOrgDetailPanel: members, subscription, and flag overrides always; usage summary and audit tail only when those features are present.
  • Impersonationadmin.impersonate stashes the admin session and starts a target session (Session.impersonatedBy); admin.stopImpersonating runs on protectedProcedure (the live session is the impersonated user). The ImpersonationBanner is mounted by the (app) layout behind @feature:admin markers.
  • Overridesadmin.setPlanOverride writes Subscription.planKeyOverride only through billing's setPlanKeyOverride (Stripe untouched); admin.setFlagOverride writes/clears a FlagOverride through flags. Both are audited (admin.plan_override_set / admin.flag_override_set).
  • admin:promote CLIpromoteToAdmin(db, email) sets User.role = "admin" directly (idempotent), bootstrapping the first admin that the plugin's own role-setting requires.

Models it adds

Admin adds no new models — it gates fields on two core models, wrapped in @feature:admin markers:

ModelGated fields
Userrole, banned, banReason, banExpires (the Better Auth admin plugin columns).
SessionimpersonatedBy — the id of the admin acting as this user (a plain column, not a relation).

Subscription.planKeyOverride and FlagOverride are not admin-owned — they prune with billing and flags. Admin only writes them through those features' seams. The admin() plugin registration in auth.ts is marker-wrapped to match.

Env it needs

None. Adds the admin:promote script (pnpm admin:promote <email>), a standalone tsx entry that builds its own Prisma client and bootstraps the first admin.

Composition

  • requires: ["orgs", "billing", "flags"] — orgs for user/org search; billing for getSubscriptionSummary and setPlanKeyOverride; flags for listFlagOverrides / setFlagOverride / clearFlagOverride. All are barrel reads, never raw rows.
  • Soft seams admin consumes (injected, not required): usage's getUsageSummary and audit-log's listAuditLogEntries + recorder, wired under nested markers in createAdminRouter. Omit a seam and its panel or audit row is silently absent — admin never imports usage or audit-log directly (D-059).
  • Dependents — none; admin is a leaf.

What pruning removes

Leaving admin out drops features/admin, the /admin route group, the admin:promote script, the gated User / Session columns, the admin() plugin registration, and the marked admin wiring (router mount, seam injection, the ImpersonationBanner). Impersonation disappears entirely. Because admin requires orgs + billing + flags — and that stack transitively pulls in email and jobs — admin cannot be added to a build lacking that whole subtree; but since no feature requires admin, removing admin forces nothing else out.

On this page