The API v2 plan is revised down to the work that is actually justified: a CSP hardening pass and an in-place domain split of the monolithic route wiring. - Auth merge (httpOnly cookies -> bearer + rotating refresh for every client) is removed and re-filed as deferred behind trigger conditions. httpOnly+SameSite is the stronger model, session.service.js already unifies cookie and bearer, the SSO/PKCE transaction cookies survive any merge, and it dragged the admin SSE fetch/ReadableStream rewrite along as a dependency for no user-visible payoff. A revival must first spec refresh-token reuse detection and a rollback procedure. - No parallel /api/v2. The URL surface is already grouped by capability, so each new router file mounts at the prefix it already owns and every URL stays byte-identical. No dual mount, no per-route migration, no v1 retirement; the SPA, Discord bot, and Android app are all untouched. API_V2_SKELETON.md is marked superseded (kept as the recipe if a versioned API is ever forced). - The /api/mobile facade and app-version floor are deferred with the revival note that it starts as a one-line alias mount, not ~70 hand-written delegates. The M11 milestone is dropped from android/PLAN.md. - Adds PR 0: a generated route manifest, so "every URL is unchanged" is proved by a zero-line diff rather than asserted in review. The baseline api-route-inventory.json (199 API routes + 2 internal) is committed here and is what PR 0's generator must reproduce byte-for-byte. - Split sequenced as five grouped PRs; CSP fixed to report-only first, then enforce (the old plan contradicted itself), with the verified delta being just form-action 'self' and frame-ancestors 'none'. Co-Authored-By: Claude <noreply@anthropic.com>
132 lines
5.0 KiB
Markdown
132 lines
5.0 KiB
Markdown
# Website API v2 — `/api/v2` Skeleton (PR 1)
|
|
|
|
> ## ⚠ Superseded — not scheduled
|
|
>
|
|
> The router domain split is being done **in place**, with every URL byte-identical, so there is no
|
|
> parallel version to stand up and this scaffold will not be built. See
|
|
> [API_V2_PLAN.md](./API_V2_PLAN.md) § Why there is no `/api/v2`.
|
|
>
|
|
> The file is kept, unedited below, as the concrete recipe **if** a real contract break ever forces a
|
|
> versioned API. Nothing here describes current or planned work.
|
|
|
|
Companion to [API_V2_PLAN.md](./API_V2_PLAN.md) — this is the concrete scaffold for **PR 1** in that
|
|
plan's sequencing. It stands up `/api/v2` **empty but wired**, next to a frozen `/api/v1`, with **no
|
|
behavior change**. Endpoints are filled in by the later PRs (auth merge, then the domain split).
|
|
|
|
## Scope
|
|
|
|
- Create the `router/v2/` tree of empty, domain-named routers.
|
|
- Mount `/v2` alongside `/v1` in `api.router.js`.
|
|
- Add a single trivial `GET /api/v2/version` so the mount is testable end-to-end.
|
|
- **Out of scope:** any real endpoint, any auth change, any controller edit. Those are PR 2+.
|
|
|
|
## File tree to create
|
|
|
|
```
|
|
website/server/src/router/v2/
|
|
v2.router.js # mounts the domain sub-routers; adds GET /version
|
|
admin/
|
|
index.js # mounts the admin capability routers under /admin
|
|
dashboard.router.js users.router.js moderation.router.js
|
|
content.router.js wiki.router.js shard.router.js
|
|
settings.router.js invites.router.js bot-activity.router.js
|
|
auth/
|
|
index.js login.router.js sso.router.js totp.router.js session.router.js
|
|
public/
|
|
index.js news.router.js wiki.router.js page.router.js shard.router.js
|
|
player/
|
|
index.js profile.router.js appeals.router.js shard.router.js
|
|
```
|
|
|
|
`internal/` is **not** part of v2's public tree — the internal routes stay on the separate,
|
|
unpublished port (`internalApp.js`), exactly as in v1. See `API_V2_PLAN.md` § Phase 2.
|
|
|
|
## Wiring
|
|
|
|
`api.router.js` gains the v2 mount next to v1:
|
|
|
|
```js
|
|
const v1Router = require('./v1/v1.router')
|
|
const v2Router = require('./v2/v2.router')
|
|
|
|
apiRouter.use('/v1', v1Router)
|
|
apiRouter.use('/v2', v2Router) // NEW — parallel version, migrate off v1 route-by-route
|
|
```
|
|
|
|
`v2.router.js` mounts each domain group and exposes the version ping:
|
|
|
|
```js
|
|
const express = require('express')
|
|
const v2Router = express.Router()
|
|
|
|
const adminRouter = require('./admin')
|
|
const authRouter = require('./auth')
|
|
const publicRouter = require('./public')
|
|
const playerRouter = require('./player')
|
|
|
|
// Cheap liveness/mount check so the parallel version is testable before any
|
|
// real endpoint exists. Returns the API major version, nothing sensitive.
|
|
v2Router.get('/version', (req, res) => res.json({ version: 2 }))
|
|
|
|
v2Router.use('/auth', authRouter)
|
|
v2Router.use('/public', publicRouter)
|
|
v2Router.use('/admin', adminRouter)
|
|
v2Router.use('/player', playerRouter)
|
|
// NOTE: /internal is intentionally NOT mounted here — same reason as v1.
|
|
|
|
module.exports = v2Router
|
|
```
|
|
|
|
Each capability router is an empty stub at this stage — a router that mounts cleanly and adds no
|
|
routes yet, so PR 2+ only has to add handlers, never re-wire:
|
|
|
|
```js
|
|
// router/v2/admin/dashboard.router.js
|
|
const express = require('express')
|
|
const router = express.Router()
|
|
|
|
// Routes added in the admin domain-split PR (see API_V2_PLAN.md § Phase 2).
|
|
|
|
module.exports = router
|
|
```
|
|
|
|
Each `index.js` mounts its group's capability routers under the URL that names them, e.g.:
|
|
|
|
```js
|
|
// router/v2/admin/index.js
|
|
const express = require('express')
|
|
const admin = express.Router()
|
|
|
|
admin.use('/dashboard', require('./dashboard.router'))
|
|
admin.use('/users', require('./users.router'))
|
|
admin.use('/moderation', require('./moderation.router'))
|
|
admin.use('/content', require('./content.router'))
|
|
admin.use('/wiki', require('./wiki.router'))
|
|
admin.use('/shard', require('./shard.router'))
|
|
admin.use('/settings', require('./settings.router'))
|
|
admin.use('/invites', require('./invites.router'))
|
|
admin.use('/bot-activity', require('./bot-activity.router'))
|
|
|
|
module.exports = admin
|
|
```
|
|
|
|
## Acceptance criteria
|
|
|
|
- Server boots with no error; every sub-router mounts.
|
|
- `GET /api/v2/version` → `200 { "version": 2 }`.
|
|
- `GET /api/v1/**` behavior is **byte-for-byte unchanged** — v1 is untouched.
|
|
- Existing server tests stay green (`cd website/server && npm test`).
|
|
- A new test asserts the `/api/v2/version` mount (smallest possible coverage of the wiring).
|
|
|
|
## Docs / spec
|
|
|
|
- Swagger regeneration is deferred until v2 has real routes (PR 2) — a lone `/version` ping doesn't
|
|
need an annotation. When PR 2 lands, add `#swagger.*` to the new routes and run `npm run swagger`.
|
|
- No `BACKEND_DESIGN.md` change here beyond noting the parallel `/api/v2` mount exists; the
|
|
route-map/security-contract edits land with the PRs that add real endpoints.
|
|
|
|
## Next
|
|
|
|
PR 2 fills the `auth/` routers with the bearer access + refresh flow and drops the session cookie —
|
|
see [API_V2_PLAN.md](./API_V2_PLAN.md) § Phase 1.
|