// /api/v1/admin — the admin surface, assembled from per-capability routers. // // This file owns exactly two things: the gate every admin route shares, and the // mount table. No route is declared here. Each capability router mounts at the // prefix it already owned inside the old monolithic admin.routes.js, so the // emitted URL set is byte-identical — proved per PR by a zero-line diff in // server/routes.manifest.json (`npm run routes:manifest`). // // The admin group is fully split as of PR 4: admin.routes.js is gone and every // one of the 110 admin routes is declared in a capability router below. // // See docs/website/API_V2_PLAN.md § Phase 2 for the split. const express = require('express') const { isLoggedIn, requireRole } = require('../../../utils/auth') const noindex = require('../../../middleware/noindex') const accountRouter = require('./account.router') const usersRouter = require('./users.router') const invitesRouter = require('./invites.router') const authProvidersRouter = require('./authProviders.router') const moderationRouter = require('./moderation.router') const botActivityRouter = require('./botActivity.router') const activityRouter = require('./activity.router') const postsRouter = require('./posts.router') const uploadsRouter = require('./uploads.router') const wikiRouter = require('./wiki.router') const pagesRouter = require('./pages.router') const shardRouter = require('./shard.router') const uoLinkRouter = require('./uoLink.router') const emailRouter = require('./email.router') const discordBotRouter = require('./discordBot.router') const settingsRouter = require('./settings.router') const dashboardRouter = require('./dashboard.router') const adminRouter = express.Router() // Every admin route requires auth, a STAFF role, and is kept out of search // indexes. The staff gate matters now that `player` is a logged-in-but-untrusted // role: without it, the editor-tier routes below (dashboard, posts, wiki, // uploads) that are only guarded by isLoggedIn would be reachable by players. // Players get 403 here and use the self-scoped /player group instead. // // It lives here, ahead of every mount, so a capability router extracted in a // later PR cannot silently ship without it. const staffOnly = requireRole('admin', 'editor', 'moderator') adminRouter.use(noindex, isLoggedIn, staffOnly) adminRouter.use('/account', accountRouter) adminRouter.use('/users', usersRouter) adminRouter.use('/invites', invitesRouter) // Mounted at /auth, not /auth/providers: /admin/auth is the capability, and the // routes inside read as /providers[/:id]. adminRouter.use('/auth', authProvidersRouter) // /moderation carries its own moderator gate; /bot-activity is admin-only per // route. /activity is staff-wide — the audit log, not the bot-scoring state. adminRouter.use('/moderation', moderationRouter) adminRouter.use('/bot-activity', botActivityRouter) adminRouter.use('/activity', activityRouter) // Content, all editor-tier (no gate beyond staffOnly above). /uploads is the // rich-text editors' generalized upload; /posts owns its own /posts/upload. adminRouter.use('/posts', postsRouter) adminRouter.use('/uploads', uploadsRouter) adminRouter.use('/wiki', wikiRouter) adminRouter.use('/pages', pagesRouter) // Ops and configuration. /shard mixes tiers on one prefix — self-service game // account linking (no extra gate) alongside modAccess in-game staff ops — so // one router owns the prefix and gates per route. The rest are admin-only. // /admin/shard/pages is the in-game help-page queue, unrelated to /admin/pages. adminRouter.use('/shard', shardRouter) adminRouter.use('/uo-link', uoLinkRouter) adminRouter.use('/email', emailRouter) adminRouter.use('/discord-bot', discordBotRouter) adminRouter.use('/settings', settingsRouter) // The two singletons that own no path segment of their own: GET /dashboard and // PUT /site-mode. Mounted at the group root, last, exactly where the residual // admin.routes.js used to sit — safe because dashboard.router.js declares no // router-level middleware, only its two routes. adminRouter.use('/', dashboardRouter) module.exports = adminRouter