Skip to content

Admin Panel Overview

Location: apps/admin/

Tech stack: Vite 7 + React 19 + Ant Design 5 + TanStack Query + React Router v7

The admin panel is a warm parchment-themed static SPA (#F7F4ED background) used by moderators and admins for content moderation, user management, governance, and platform analytics. Served from the single VPS behind Cloudflare (no Vercel/Next.js).

API base: All calls go to GET /api/v1/admin/* unless noted.


Authentication & RBAC

Login via POST /identity/login with role = 'admin' or role = 'moderator'. JWT stored in memory (AuthProvider). ProtectedShell redirects to /login when unauthenticated.

Moderator = read-only. Moderators get GET-only access — they can inspect every surface but cannot mutate (ban, hide, expel, resolve, force-close, etc.). All write actions are gated to admin.


Pages

Dashboard (/)

Endpoints used:

  • GET /admin/metrics — cached 30s server-side

Metrics shown:

  • users — total registered users
  • postsTotal — all posts
  • invitesActive — active invite codes
  • reportsOpen — unresolved reports
  • postsHidden — moderator-hidden posts
  • repliesHidden — moderator-hidden replies
  • mauActive — monthly active users

Attention queue: shows when reportsOpen + postsHidden + repliesHidden > 0.


Invite Codes (/invite-codes)

Endpoints: GET /admin/invite-codes, POST /admin/invite-codes/generate, POST /admin/invite-codes/:id/revoke

Columns: code, status, generated by, reserved until, used by, used at, expires at. Actions: Revoke active codes, generate admin-issued codes.


Users (/users + /users/:id)

Endpoints: GET /admin/users, GET /admin/users/:id, POST /admin/users/:id/ban, POST /admin/users/:id/unban, PATCH /admin/users/:id/role

List: id, username, email, role, banned status, created at, post/circle count. User detail (/users/:id):

  • Profile info
  • Role selector (user/moderator/admin)
  • Ban/unban with reason
  • Reputation history
  • Invite tree position
  • Posts list

Circles (/circles)

Endpoints: GET /admin/circles, DELETE /admin/circles/:id

Columns: id, owner, kind, name, member count, created at. Actions: Delete circle (admin only, emits audit log entry).


Content Moderation (/content)

Endpoints: GET /admin/content?kind=…, POST /admin/content/:kind/:id/remove, POST /admin/content/:kind/:id/restore

A single Segmented switcher over 8 UGC kinds from the Wave 3-6 content contexts:

events · debates · problems · courses · projects · videos · audio · offerings (Market)

Per row: status-coloured sub badge, owner, interest, created at. Actions: remove (soft-hide) / restore. See Content Contexts.


Groups (/groups + detail)

Endpoints: GET /admin/groups, GET /admin/groups/:id, group member + join-request mutations.

List: name, kind, member count, created at. Detail tabs:

  • Members — roster with kick action.
  • Join requests — pending requests with approve / reject.

Guilds (/guilds + detail)

Endpoints: GET /admin/guilds, GET /admin/guilds/:id, archive/restore, plus member / application / proposal mutations.

List: name, join policy (invite-only / open), minReputation, member count, open proposals, pending applications. Detail tabs:

  • Members — roster with expel action.
  • Applications — pending join applications with approve / reject (badge count).
  • Proposals — governance proposals with status badges and force-close.

See Guilds for the governance model.


Posts (/posts)

Endpoints: GET /admin/posts, POST /admin/posts/:id/hide, POST /admin/posts/:id/unhide, DELETE /admin/posts/:id

Columns: title, author, format, interest, visibility, depth, journalism, boost score, reactions, replies, created at, hidden. Filters: format, interest, visibility, journalism, hidden only. Actions: Hide/unhide, soft-delete. The same in-app hide-post and ban-author levers are also exposed inline from the detail view.

UUID-safe search. The posts and replies lists accept a free-text q. Non-UUID search terms no longer 500 the endpoint — the query distinguishes id lookups from text matches safely.


Replies (/replies)

Endpoints: GET /admin/replies, POST /admin/replies/:id/hide, POST /admin/replies/:id/unhide

Columns: body snippet, kind, author, post, created at, hidden. Actions: Hide/unhide.


Reports (/reports)

Endpoints: GET /admin/reports, POST /admin/reports/:id/review, POST /admin/reports/:id/dismiss

Columns: target type/id, reason, reporter, status, created at. Filters: status (open/reviewed/dismissed), target type. Actions: Review (sets reviewed_by + timestamp), Dismiss.


Reputation (/reputation)

Endpoints: GET /admin/reputation/events, POST /admin/reputation/adjust, GET /admin/reputation/appeals, POST /admin/reputation/appeals/:id/resolve

Tabs:

  • Events: Reputation ledger across all users.
  • Adjust: Manual rep adjustment { userId, interestId, delta, note }.
  • Appeals: Pending/accepted/rejected appeals. Resolve with optional restoreDelta.
  • Peer Votes: GET /admin/reputation/votes — view all peer votes for moderation.

Contribution (/contribution)

Endpoints: GET /admin/contribution

Shows top contributors by score. Columns: user, total score, breakdown by kind.


Economy (/economy)

Endpoints: GET /admin/economy/wallets, GET /admin/economy/payments

Tabs:

  • Wallets: All wallets sorted by balance. Columns: user, balance, lifetime_earned, lifetime_spent.
  • Payments: Payment ledger. Columns: sender, receiver, emotion, tier, amount, post, reversed, created at.

Audit Log (/audit-log)

Endpoints: GET /admin/audit-log

All privileged mutations. Columns: action, actor, target_type, target_id, ip, created at. Filters: action type, actor, date range.


Conversations (/conversations)

Endpoints: GET /admin/conversations

Columns: kind, title, member count, message count, created at. Actions: View members (no message content access — privacy).


AI Sessions (/assistant-sessions)

Endpoints: GET /admin/assistant-sessions

Columns: user, topic, status, turn count, started at, completed at.


Discovery Intents (/intents)

Endpoints: GET /admin/intents

Columns: user, intent, note, isActive, updated at.


Media (/media)

Endpoints: GET /admin/media

Columns: owner, bucket, key, mime type, size, status, created at. Actions: Mark as reviewed, soft-delete orphans.


Interests (/interests + /interests/:idOrSlug)

Endpoints: GET /admin/interests, POST /admin/interests, PATCH /admin/interests/:id

List all interests with level, slug, parent, user count. Interest detail: top users by reputation, recent posts, wiki entry.


Series (/series)

Endpoints: GET /admin/series

Columns: title, author, item count, follower count, created at.


Wiki Proposals (/wiki)

Endpoints: GET /admin/wiki/proposals, POST /admin/wiki/proposals/:id/approve, POST /admin/wiki/proposals/:id/reject

Columns: interest, proposer, status, net votes, summary, created at. Actions: Approve (creates new WikiEntry version), Reject.


Settings (/settings)

Endpoints: GET /admin/feature-flags, PATCH /admin/feature-flags/:key

Manage feature flags (e.g., emoji-pay, wiki, personas-ui). Also: broadcast message to all users POST /admin/broadcast { title, body }.


Broadcast Modal

Global button in header for admins. Sends POST /admin/broadcast { title, body }:

  • Creates Notification rows for all non-banned users.
  • Emits admin.broadcast.sent → push fan-out via PushService.

Theme Tokens

typescript
colorPrimary: '#1F1B16'
colorBgBase: '#F7F4ED'     // warm parchment
colorBgContainer: '#FFFFFF'
colorBorder: '#E6DFCF'
Menu.itemSelectedBg: '#EFE9D7'
Table.rowHoverBg: '#EFE9D7'

Regulus — invite-only social-knowledge platform