refactor(server): split public, player and residual auth into capability routers (PR 5)
The last split PR of docs/website/API_V2_PLAN.md § Phase 2. public.routes.js,
player.routes.js and auth.routes.js are deleted; each group is now a directory
whose index.js owns the group gate and the mount table and declares no routes.
Every one of the 200 manifest routes is now in a capability router.
public/ posts (2) wiki (4) pages (2) shard (12) site (4, group root)
player/ account (8) shard (8) appeals (4), behind noindex + requireAuth
auth/ login (2) register (1) invite (2) password (3) session (2, root)
No URL moves. All four gates zero-diff: routes.manifest.json (200 public + 2
internal), routes.guards.json, swagger-output.json (198 operations), and
docs/website/api-route-inventory.json was already in sync. 434 tests green.
Notes on the non-mechanical parts:
- public/index.js and auth/index.js carry no group gate, deliberately, and say
so. The public surface is anonymous by contract (logged-out SPA, Discord bot,
Android ShardStreamClient on /public/shard/stream); /auth is where a caller
becomes authenticated. player/index.js gates on requireAuth only, never
requireRole('player') — staff are a superset of players.
- GET /auth/me has a mount-order dependency: use('/me', meRouter) matches the
bare /me, so the request runs meRouter's noindex + requireAuth and falls
through. session.router.js must stay mounted last. Verified by the
counterfactual — mounting it first still 401s but drops X-Robots-Tag, which
no manifest or guards file can see.
- loginGuards moved to auth/loginGuards.js (frozen) rather than being copied
into the three routers that spread it; sso.routes.js drops its duplicate.
- The :param shadowing check was re-run in dispatch order against the built
stack: 86 routes, 64 literal, none shadowed. /public/wiki/{categories,tags}
ahead of /:slug is the only ordering-sensitive pair.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
43
server/src/router/v1/public/index.js
Normal file
43
server/src/router/v1/public/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// /api/v1/public — the anonymous public surface, assembled from per-capability
|
||||
// routers.
|
||||
//
|
||||
// This file owns the mount table and nothing else; no route is declared here.
|
||||
// Each capability router mounts at the prefix it already owned inside the old
|
||||
// monolithic public.routes.js, so the emitted URL set is byte-identical — proved
|
||||
// by a zero-line diff in server/routes.manifest.json (`npm run routes:manifest`).
|
||||
//
|
||||
// **There is deliberately no group gate.** Unlike /admin (staffOnly) and /player
|
||||
// (requireAuth), this group is unauthenticated by design and must stay that way:
|
||||
// the SPA renders logged-out, the Discord bot reads it anonymously, and the
|
||||
// Android app's ShardStreamClient consumes /public/shard/stream with no
|
||||
// Authorization header. Content visibility during maintenance is handled by the
|
||||
// per-route `siteMode` middleware, not by an auth gate.
|
||||
//
|
||||
// See docs/website/API_V2_PLAN.md § Phase 2 for the split.
|
||||
|
||||
const express = require('express')
|
||||
|
||||
const postsRouter = require('./posts.router')
|
||||
const wikiRouter = require('./wiki.router')
|
||||
const pagesRouter = require('./pages.router')
|
||||
const shardRouter = require('./shard.router')
|
||||
const siteRouter = require('./site.router')
|
||||
|
||||
const publicRouter = express.Router()
|
||||
|
||||
// Content. All three are site-mode gated per route (the /pages draft-preview
|
||||
// route is the one deliberate exception — see pages.router.js).
|
||||
publicRouter.use('/posts', postsRouter)
|
||||
publicRouter.use('/wiki', wikiRouter)
|
||||
publicRouter.use('/pages', pagesRouter)
|
||||
// Live shard data, never site-mode gated.
|
||||
publicRouter.use('/shard', shardRouter)
|
||||
|
||||
// The four singletons that own no path segment of their own: /settings, /status,
|
||||
// /version and /contact. Mounted at the group root, last — safe only because
|
||||
// site.router.js declares no router-level middleware (a bare `use(gate)` in a
|
||||
// root-mounted router runs for every request passing through toward another
|
||||
// mount). Same arrangement as admin/dashboard.router.js.
|
||||
publicRouter.use('/', siteRouter)
|
||||
|
||||
module.exports = publicRouter
|
||||
Reference in New Issue
Block a user