PR 4 of the in-place admin router split (docs/website/API_V2_PLAN.md § Phase 2),
and the last admin one: it moves the entire residual 33 and DELETES
admin.routes.js. Every one of the 110 admin routes is now declared in a
capability router. No URL, gate or handler changes.
shard.router.js (16) /admin/shard
uoLink.router.js ( 5) /admin/uo-link
email.router.js ( 6) /admin/email
discordBot.router.js ( 2) /admin/discord-bot
settings.router.js ( 2) /admin/settings
dashboard.router.js ( 2) GET /dashboard + PUT /site-mode, at the group root
admin.routes.js deleted, was 33
No gate moved to router level. Every adminOnly in the residual file was
per-route, and modAccess on /shard must stay per-route because half that router
must not have it — which keeps the per-route handler count intact, the one
number routes.guards.json can actually check.
/shard is the first prefix where two tiers share one router: 7 self-service
account-linking routes (no extra gate, served by the same player/shard
controller handlers, tagged `Admin · Account`) alongside 9 in-game staff ops on
modAccess. Prefix ownership beats tag grouping — splitting by tag would put two
routers under one prefix for no gain. The tag mismatch stays; retagging is a
real spec diff and belongs in a PR about tags.
dashboard.router.js is the one router mounted at the group root rather than a
prefix: GET /dashboard and PUT /site-mode share no path segment. That is safe
only because the file declares no router-level middleware — a bare use(gate) in
a root-mounted router would run for every request passing through toward
another mount. The file carries a comment saying so.
Acceptance — all four gates zero-diff:
routes.manifest.json unchanged (200 public + 2 internal)
routes.guards.json unchanged (no route lost or gained a gate)
swagger-output.json unchanged (198 operations)
api-route-inventory.json already in sync
plus 434 server tests green.
Verified separately, because no gate can catch it: introspecting the built
stack, all 59 literal admin paths still dispatch to their own layer — nothing
is captured first by a /:param sibling. The manifest sorts its entries, so
declaration order is invisible to it.
Also repoints the comments that referenced admin.routes.js by name
(botActivity/moderation controllers, the town-crier cap mirror in
announceJobs.logic.js) and generalizes the "the path is on the line after
router.get(" rationale in routeManifest.js, README.md and pr-checks.yml, which
was never about that one file.
Co-Authored-By: Claude <noreply@anthropic.com>
84 lines
4.1 KiB
JavaScript
84 lines
4.1 KiB
JavaScript
// /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
|