Admin
Super Admin Portal

Super Admin Portal

Platform-operator-only control panel at /admin/* for managing every tenant (stores, users, subscriptions, revenue, health) across the Eziseller install. Audience: new dev with Node/React experience, no Eziseller context.

1. Overview

Regular owners and staff operate inside a single tenant boundary (their store, scoped by ownerId). The Super Admin portal is the cross-tenant surface: it lets platform operators see every store, deactivate abusers, override subscriptions, create more admins, and monitor platform-wide health. It is mounted at /admin/* on the frontend and /api/admin/* on the backend, and is gated end-to-end by the SUPER_ADMIN role. There are exactly seven sections: Overview, Stores, Users, Subscriptions, Revenue, System Health, Settings. It deliberately does not reuse the tenant dashboard routes — admin endpoints are separate so their queries can skip ownerId scoping without risking a leak into tenant code paths.

2. Architecture

AdminGuard only hides UI — the real authorization is authenticateToken + requireSuperAdmin applied once at the router root in backend/routes/admin/index.ts:23. Every admin sub-router inherits that gate, so individual endpoints never re-check the role.

3. Data model

SUPER_ADMIN users have no ownedStore and no ownerId — they exist outside the tenant graph. All tenant scoping in the rest of the backend keys off ownerId; admin routes simply omit that filter. See schema.prisma.

4. Sections & key endpoints

4.1 Overview (/admin)

High-level dashboard. Pulls from GET /api/admin/subscriptions/overview (status breakdown, trials expiring in 7 days, count-by-plan, total stores/users).

4.2 Stores (/admin/stores)

  • GET /api/admin/stores — paginated list with search on name/slug, includes owner + product/order counts.
  • GET /api/admin/stores/:id — detail with owner, latest subscription, counts.
  • PATCH /api/admin/stores/:id/toggle-active — flips Store.isActive. Not currently enforced anywhere — see gotchas.

4.3 Users (/admin/users)

  • GET /api/admin/users — paginated list, filterable by role/isActive, search on name/email.
  • GET /api/admin/users/:id — detail with store, latest subscription, counts.
  • PATCH /api/admin/users/:id/toggle-active — refuses on SUPER_ADMIN.
  • DELETE /api/admin/users/:id — hard delete, refuses on SUPER_ADMIN.
  • POST /api/admin/users/:id/impersonate — mints access + refresh tokens for the target user; refuses on SUPER_ADMIN. Adds the refresh token to the in-memory refreshTokenStore.

4.4 Subscriptions (/admin/subscriptions)

  • GET /api/admin/subscriptions/overview — aggregates for the Overview page.
  • GET /api/admin/subscriptions — paginated, filters: status, billingCycle, planSlug.
  • PATCH /api/admin/subscriptions/:id/override — the god-mode endpoint. Accepts any of planSlug, status, billingCycle, extendDays and writes directly to UserSubscription. Bypasses Razorpay entirely.

4.5 Revenue (/admin/revenue)

  • GET /api/admin/revenue — total/this-month/last-month revenue from SubscriptionPayment (status=success), MRR (monthly plans + yearly/12), trial-to-paid conversion %, 30-day churn %, active count by plan. Pure read, ~11 parallel Prisma queries.

4.6 System Health (/admin/health)

  • GET /api/admin/health — draft orders with parse errors (raw SQL on parseErrors array), expired/rejected counts, subs expiring/expired, 7-day user+order velocity, notification queue breakdown + 10 most-recent failures.

4.7 Settings (/admin/settings)

  • PUT /api/admin/settings/change-password — changes the calling admin's own password.
  • POST /api/admin/settings/create-admin — creates another SUPER_ADMIN (no store, no subscription).
  • GET /api/admin/settings/admins — lists all super admins.

4.8 Jobs & cron (also mounted on admin root)

  • POST /api/admin/jobs/update-expired-subscriptions — manually triggers the expired-trial job.
  • GET /api/admin/cron/status, POST /api/admin/cron/restart — manages the subscription cron scheduler.

5. Key files

6. Env vars & config

VarRequiredPurposeWhat breaks
JWT_SECRETyesSigns admin access tokens (incl. impersonation tokens)Admin login + impersonation fail
DATABASE_URLyesAll admin reads/writesEntire portal 500s
FRONTEND_URLyesCORS for /admin/* fetchesBrowser CORS errors from admin UI

There is no separate admin env var — role lives in the DB on User.role. Seeding the first SUPER_ADMIN is done manually (SQL or a seed script), then create-admin bootstraps the rest.

7. Gotchas & troubleshooting

  • Store.isActive=false does nothing yet. The stores toggle-active endpoint flips the flag, but no tenant-side middleware checks it — deactivated stores still serve requests. Enforcement is deferred.
  • SubscriptionGate does not block impersonation. When a super admin impersonates a tenant, the minted token carries the target user's role but SubscriptionGate is not re-entered with the admin context, so expired-subscription users can still be impersonated into a dashboard that is normally gated. Deferred.
  • Admin queries bypass ownerId scoping by design. Every other backend route filters by req.user.ownerId || req.user.userId. Admin routes do not. Copy-pasting a snippet from routes/orders.ts into routes/admin/* is fine; copy-pasting the other direction will leak cross-tenant data. Keep admin-scoped logic in routes/admin/ only.
  • Impersonation refresh tokens live in an in-memory Set. refreshTokenStore is reset on backend restart (see routes/auth). Impersonation sessions survive a refresh window only as long as the process stays up.
  • Health endpoint must stay fast. /api/admin/health fans out ~10 queries including a raw SQL on draft_orders.parseErrors. Do not add unbounded findMany calls without take: — this page is polled and a slow query will hang the whole admin shell.
  • AdminGuard is cosmetic only. It just hides UI and redirects. Never rely on it for authorization — a user hitting /api/admin/* directly is stopped by requireSuperAdmin, not by the React guard.
  • create-admin does not send an email or require email verification. The new SUPER_ADMIN can log in immediately with the password you set. Treat it like a privileged bootstrap operation.
  • Revenue numbers depend on SubscriptionPayment.status='success'. Failed/pending payments are excluded. If Razorpay webhook handling is broken, revenue will under-report silently.

8. Extension points

  • New admin section: add backend/routes/admin/<name>.ts, mount it in backend/routes/admin/index.ts, add a nav entry in src/app/admin/layout.tsx, and create src/app/admin/<name>/page.tsx. The root authenticateToken + requireSuperAdmin applies automatically — do not re-add it.
  • Audit log: there is currently no persisted audit trail; destructive actions only console.log. A cross-cutting audit middleware on the admin router is the natural place to add one.
  • Store deactivation enforcement: add a check in middleware/subscription.ts (or a new requireActiveStore middleware) that rejects requests when user.ownedStore.isActive === false.

9. Related docs