Companion doc to API_V2_PLAN.md describing PR 1: stand up router/v2/ empty but wired next to a frozen /api/v1, with a trivial GET /api/v2/version to make the mount testable and no behavior change. Includes the file tree, the api.router.js / v2.router.js wiring, empty capability-router stubs, and acceptance criteria. Adds a forward link from the plan's PR-1 line to the skeleton doc. Co-Authored-By: Claude <noreply@anthropic.com>
4.5 KiB
Website API v2 — /api/v2 Skeleton (PR 1)
Companion to 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
/v2alongside/v1inapi.router.js. - Add a single trivial
GET /api/v2/versionso 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:
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:
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:
// 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.:
// 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/versionmount (smallest possible coverage of the wiring).
Docs / spec
- Swagger regeneration is deferred until v2 has real routes (PR 2) — a lone
/versionping doesn't need an annotation. When PR 2 lands, add#swagger.*to the new routes and runnpm run swagger. - No
BACKEND_DESIGN.mdchange here beyond noting the parallel/api/v2mount 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 § Phase 1.