Files
website/server/src/router/v1/public/index.js
wtclaude 565a7d2c20
All checks were successful
PR Checks / bot-install (pull_request) Successful in 17s
PR Checks / client-build (pull_request) Successful in 25s
PR Checks / server-tests (pull_request) Successful in 9m20s
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>
2026-07-27 20:52:14 -05:00

44 lines
2.0 KiB
JavaScript

// /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