refactor(server): split admin users, account, invites and auth providers into capability routers
First of the five domain-split PRs in docs/website/API_V2_PLAN.md § Phase 2. Pure
mechanical re-wiring: routes move between files, no handler, gate, validator or
annotation changes, and not one URL moves.
New src/router/v1/admin/index.js owns the two things the group shares — the
`noindex, isLoggedIn, staffOnly` gate and the mount table — and declares no routes
itself. The gate sits ahead of every mount so a capability router extracted in a
later PR cannot silently ship without it. Four capability routers mount at the
prefix they already owned inside the monolith:
account.router.js 6 routes -> /admin/account (self-service, no adminOnly)
users.router.js 15 routes -> /admin/users (adminOnly, router-level)
invites.router.js 3 routes -> /admin/invites (adminOnly, per-route)
authProviders.router.js 4 routes -> /admin/auth (adminOnly, per-route)
admin.routes.js keeps the other 82 (6+15+3+4+82 = the 110 inventoried admin
routes) and is mounted last at the group root; none of the four prefixes appears
in it, so nothing depends on mount ordering. It disappears when PR 5 lands.
Handlers still live in admin.controller.js and usersShard.controller.js — this
re-wires routes, not logic. `adminOnly` moves with the routes that use it, and
`usersRouter.use(adminOnly)` is exactly equivalent to the old
`adminRouter.use('/users', adminOnly)` now that the router is mounted at /users.
All three generated gates are 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, byte-for-byte
The spec staying byte-identical depends on the path normalization landed in the
preceding commit; without it the four collection routes would have documented as
/api/v1/admin/{users,invites,account}/ with a trailing slash.
Server tests green (434/434).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
46
server/src/router/v1/admin/index.js
Normal file
46
server/src/router/v1/admin/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// /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`).
|
||||
//
|
||||
// See docs/website/API_V2_PLAN.md § Phase 2 for the split and its remaining PRs.
|
||||
|
||||
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 residualRouter = require('./admin.routes')
|
||||
|
||||
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)
|
||||
|
||||
// Everything not yet extracted, at the group root. Mounted last, but none of the
|
||||
// prefixes above appear in it, so nothing here depends on the ordering.
|
||||
adminRouter.use('/', residualRouter)
|
||||
|
||||
module.exports = adminRouter
|
||||
Reference in New Issue
Block a user