Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesAdmin AppAdmin App — Behavior

Admin App — Behavior

Authentication

  • Clerk-managed. The SPA initializes ClerkProvider with VITE_CLERK_PUBLISHABLE_KEY and falls back to a hash-routed <SignIn /> when signed out.
  • Refine authProvider wraps useAuth(); getToken() is threaded into every API call via the custom dataProvider.
  • Authorization: every /admin/** API call passes through PlatformAdminGuard (apps/api/src/admin/guards/platform-admin.guard.ts):
    1. Read clerkId from request.auth.userId.
    2. Cache lookup platform-admin:<clerkId> in Redis (5 min TTL).
    3. On cache miss, fetch the Clerk user and check privateMetadata.platformRole === 'admin'.
    4. Cache the result (true or false) for 5 min.
    5. Throw ForbiddenException otherwise.

A user demoted in Clerk continues to have access for up to 5 minutes — acceptable for a tool that’s used by trusted internal staff only. The cache key is invalidated manually via the admin user detail page when a demotion needs to take effect immediately.

Audit logging

Every state-changing call to /admin/** flows through AdminAuditInterceptor. The interceptor:

  • Captures actor clerkId, method, URL, body.
  • Writes to audit_logs with actorClerkId, action, resource, resourceId, metadata.{ adminAction: true, ip, userAgent, ... }.
  • Pipes the entry through EventTrackingService to PostHog so admin actions show on the same funnel as agent actions.

Reads are not audit-logged.

Pages

Organizations

GET /admin/organizations — list with pagination, filters (tier, status, country, search). GET /admin/organizations/:id — full detail including members, subscriptions, plans, minisite status, recent audit entries. Tier updates: POST /admin/organizations/:id/tier body { tier }, audited.

Users

GET /admin/users — cross-org user search. GET /admin/users/:id — profile, memberships, payment cards. Actions: password reset (POST /admin/users/:id/reset-password), soft-delete, force email update.

Payments + subscriptions

  • Payment list filterable by provider, status, org, date range.
  • Refund flow requires reason in the body — recorded into the audit metadata.
  • Subscription dashboard surfaces next_charge_date and overdue cohorts.

Leads

Cross-org lead pipeline. Owners reassign leads to orgs (e.g. a waitlist lead asking for a specific gym).

Jobs

Lists imports, exports, embedding, AI enrichment jobs across all orgs. Pause / requeue / drop helpers.

Audit Logs

Filterable by actor, resource, timestamp, metadata.agent, metadata.adminAction. The view is the canonical “what happened” timeline.

Queues

BullMQ status (waiting, active, completed, failed, delayed counts). Actions: pause, resume, drain. Powered by AdminQueuesService.

System

Server uptime, memory, Redis ping, Postgres + R2 health, env summary (no secrets).

Costs

Daily and monthly spend across providers (Anthropic, Voyage, Resend, etc.). Per-org breakdown surfaces cost outliers.

Billing inspection

Per-org invoice and balance state. Surfaces debt, past-due, cancellation requests.

Platform Billing

FitKit’s own income side. Lists platform transactions (apps/api/src/admin/controllers/admin-platform-billing.controller.ts), MRR, tier distribution, cancelled platform subscriptions. Includes a cancel-platform-txn.dto.ts action for refunding FitKit’s invoices.

Observability

Agent trace pivot, recent Sentry issues (linked out), PostHog quick links. Lightweight — heavy analysis lives in the underlying tools.

Actions

One-off operational hooks under apps/api/src/admin/controllers/admin-actions.controller.ts:

  • Rebuild cache (rebuild-cache.dto.ts).
  • Reindex embeddings (reindex.dto.ts).
  • Fix bookings (fix-bookings.dto.ts).
  • Manual cost entry (manual-cost.dto.ts).
  • Each is audited.

Impersonate

POST /admin/impersonate/:userId returns a short-lived Clerk impersonation token. Admin loads the dashboard as the target user; the audit log carries impersonatedUserId. Used sparingly for ticket resolution.

Canonical Movements

Page for curating the canonical exercise library (used by the embedding + Spotter resolve flows). Editors review enrichment output, edit muscle/equipment tags, dedupe duplicates.

Deploy

  • Vercel project, separate from apps/web.
  • Build: pnpm exec nx build admin-appvite build → SPA assets under dist/.
  • Hosted at a private subdomain; access is gated at app level (Clerk) — no IP allowlist.

Limits

  • No mobile layout — Ant Design table density assumes desktop.
  • Real-time updates (queues, jobs) poll every 10s; no SSE / WebSocket.
  • Bulk operations are limited to what the API exposes; no client-side select-all + apply pattern beyond what Refine ships.