docs(website): record the PR 2 admin router split (moderation, bot-activity, activity) #54
@@ -1,6 +1,6 @@
|
|||||||
# Website API — router domain split + CSP hardening
|
# Website API — router domain split + CSP hardening
|
||||||
|
|
||||||
Status: **in progress** — PR 0 (route manifest), CSP report-only, and split PR 1 of 5 have landed ·
|
Status: **in progress** — PR 0 (route manifest), CSP report-only, and split PRs 1–2 of 5 have landed ·
|
||||||
Target repo: `website/` · Docs owner: this file + `BACKEND_DESIGN.md`
|
Target repo: `website/` · Docs owner: this file + `BACKEND_DESIGN.md`
|
||||||
|
|
||||||
> **This file replaces the earlier "API v2" plan** (auth merge → CSP → domain split, with a parallel
|
> **This file replaces the earlier "API v2" plan** (auth merge → CSP → domain split, with a parallel
|
||||||
@@ -273,7 +273,8 @@ router/v1/
|
|||||||
posts.router.js pages.router.js wiki.router.js
|
posts.router.js pages.router.js wiki.router.js
|
||||||
uploads.router.js shard.router.js uoLink.router.js
|
uploads.router.js shard.router.js uoLink.router.js
|
||||||
email.router.js discordBot.router.js settings.router.js
|
email.router.js discordBot.router.js settings.router.js
|
||||||
dashboard.router.js # + the /activity and /site-mode singletons
|
activity.router.js # the staff audit log — landed in PR 2, not with dashboard
|
||||||
|
dashboard.router.js # + the /site-mode singleton
|
||||||
auth/
|
auth/
|
||||||
login.router.js register.router.js password.router.js invite.router.js
|
login.router.js register.router.js password.router.js invite.router.js
|
||||||
sso.router.js mobile.router.js me.routes.js (already split, 23 routes)
|
sso.router.js mobile.router.js me.routes.js (already split, 23 routes)
|
||||||
@@ -341,6 +342,40 @@ Three findings worth carrying into PRs 2–5:
|
|||||||
|
|
||||||
The OpenAPI spec was also byte-for-byte unchanged, which required a prerequisite fix — see below.
|
The OpenAPI spec was also byte-for-byte unchanged, which required a prerequisite fix — see below.
|
||||||
|
|
||||||
|
### PR 2 — as landed
|
||||||
|
|
||||||
|
`moderation`, `bot-activity` and `activity` — 18 routes, leaving 64 in the residual file.
|
||||||
|
|
||||||
|
| Router | Routes | Prefix | Extra gate |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `moderation.router.js` | 15 | `/admin/moderation` | `modAccess` (admin + moderator) at router level |
|
||||||
|
| `botActivity.router.js` | 2 | `/admin/bot-activity` | `adminOnly` per route |
|
||||||
|
| `activity.router.js` | 1 | `/admin/activity` | none — staff-wide audit log |
|
||||||
|
| `admin.routes.js` (residual) | 64 | group root, mounted last | unchanged |
|
||||||
|
|
||||||
|
All four gates came back 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 server tests green.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- **`/activity` gets its own file, deviating from the target tree above**, which parked it as a
|
||||||
|
singleton inside `dashboard.router.js`. It is folded into PR 2 by the sequencing list, and PR 4 is
|
||||||
|
where `dashboard` lands — so honouring the tree would have meant leaving one route in the residual
|
||||||
|
file for two PRs to satisfy a filename. It is also a genuinely separate capability: `/activity` is
|
||||||
|
the **staff audit log** (`activity.model.js`), while `/dashboard` is a stats overview and
|
||||||
|
`/bot-activity` is the botScore middleware's in-memory ban state. Three different things that read
|
||||||
|
alike. **PR 4 mounts `dashboard` and `site-mode` only.**
|
||||||
|
- **`modAccess` moved to a router-level `use`; `adminOnly` on bot-activity deliberately did not.**
|
||||||
|
Moderation was already gated by a *prefix* mount (`adminRouter.use('/moderation', modAccess)`), so
|
||||||
|
`moderationRouter.use(modAccess)` is the exact equivalent (the PR 1 `users` case). Bot-activity's
|
||||||
|
gate was per-route, and keeping it per-route is what holds the per-route handler count — the one
|
||||||
|
number in `routes.guards.json` that would catch a dropped `adminOnly`, since `requireRole(...)`
|
||||||
|
returns an anonymous arrow and never shows up by name. **Rule for PRs 3–5: move a gate to router
|
||||||
|
level only where it was already a prefix mount; otherwise leave it on the route.**
|
||||||
|
- **`modAccess` stays in the residual file** — the `/shard/*` in-game staff operations still use it
|
||||||
|
and do not move until PR 4. Its comment there was retargeted rather than deleted.
|
||||||
|
|
||||||
### The swagger path-normalization prerequisite (landed before PR 1)
|
### The swagger path-normalization prerequisite (landed before PR 1)
|
||||||
|
|
||||||
swagger-autogen builds a path by string-concatenating the mount prefix with the route argument, so a
|
swagger-autogen builds a path by string-concatenating the mount prefix with the route argument, so a
|
||||||
@@ -449,10 +484,10 @@ deliberate `+1` in the manifest — which is exactly the mechanism working as de
|
|||||||
flipping. Also decide there whether `/api/csp-report` is retired with the report-only twin or kept
|
flipping. Also decide there whether `/api/csp-report` is retired with the report-only twin or kept
|
||||||
as a `report-to` group on the enforced policy.
|
as a `report-to` group on the enforced policy.
|
||||||
4. **PR 1 — admin:** `users`, `account`, `invites`, `auth` (providers). ✅ landed
|
4. **PR 1 — admin:** `users`, `account`, `invites`, `auth` (providers). ✅ landed
|
||||||
5. **PR 2 — admin:** `moderation`, `bot-activity`, `activity`.
|
5. **PR 2 — admin:** `moderation`, `bot-activity`, `activity`. ✅ landed
|
||||||
6. **PR 3 — admin (content):** `posts`, `pages`, `wiki`, `uploads`.
|
6. **PR 3 — admin (content):** `posts`, `pages`, `wiki`, `uploads`.
|
||||||
7. **PR 4 — admin (ops/config):** `shard`, `uo-link`, `email`, `discord-bot`, `settings`, `site-mode`,
|
7. **PR 4 — admin (ops/config):** `shard`, `uo-link`, `email`, `discord-bot`, `settings`, `site-mode`,
|
||||||
`dashboard`.
|
`dashboard`. (`activity` went with PR 2 — see § PR 2 — as landed.)
|
||||||
8. **PR 5 — `public/*` + `player/*`** (and the residual `auth/*` grouping).
|
8. **PR 5 — `public/*` + `player/*`** (and the residual `auth/*` grouping).
|
||||||
|
|
||||||
Each PR: **zero-line diff in `routes.manifest.json`**, server tests green
|
Each PR: **zero-line diff in `routes.manifest.json`**, server tests green
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ Skeleton from the spec, with a small number of justified additions marked **(+)*
|
|||||||
> every URL unchanged**. This section and §4 get updated as each split PR lands. See
|
> every URL unchanged**. This section and §4 get updated as each split PR lands. See
|
||||||
> [API_V2_PLAN.md](./API_V2_PLAN.md) § Phase 2.
|
> [API_V2_PLAN.md](./API_V2_PLAN.md) § Phase 2.
|
||||||
>
|
>
|
||||||
> **Landed so far:** admin `users`, `account`, `invites` and `auth/providers` (28 routes) now live in
|
> **Landed so far:** admin `users`, `account`, `invites`, `auth/providers` (PR 1, 28 routes) and
|
||||||
> their own routers under `admin/`, behind a new `admin/index.js`. The remaining 82 admin routes are
|
> `moderation`, `bot-activity`, `activity` (PR 2, 18 routes) now live in their own routers under
|
||||||
> still in `admin.routes.js`, and `public/` and `player/` are untouched.
|
> `admin/`, behind a new `admin/index.js`. The remaining 64 admin routes are still in
|
||||||
|
> `admin.routes.js`, and `public/` and `player/` are untouched.
|
||||||
>
|
>
|
||||||
> "Every URL unchanged" is enforced mechanically, not by review: `server/scripts/routeManifest.js`
|
> "Every URL unchanged" is enforced mechanically, not by review: `server/scripts/routeManifest.js`
|
||||||
> (`npm run routes:manifest`) walks the live Express stack and writes the sorted
|
> (`npm run routes:manifest`) walks the live Express stack and writes the sorted
|
||||||
@@ -68,7 +69,12 @@ server/
|
|||||||
users.router.js (15) /admin/users — adminOnly
|
users.router.js (15) /admin/users — adminOnly
|
||||||
invites.router.js (3) /admin/invites — adminOnly
|
invites.router.js (3) /admin/invites — adminOnly
|
||||||
authProviders.router.js (4) /admin/auth — adminOnly
|
authProviders.router.js (4) /admin/auth — adminOnly
|
||||||
admin.routes.js (82) everything not yet split, mounted
|
moderation.router.js (15) /admin/moderation — modAccess
|
||||||
|
(admin+moderator) at router level
|
||||||
|
botActivity.router.js (2) /admin/bot-activity — adminOnly
|
||||||
|
activity.router.js (1) /admin/activity — staff-wide
|
||||||
|
audit log, no extra gate
|
||||||
|
admin.routes.js (64) everything not yet split, mounted
|
||||||
last at the group root; goes away
|
last at the group root; goes away
|
||||||
when the final split PR lands
|
when the final split PR lands
|
||||||
admin.controller.js + the per-capability controllers
|
admin.controller.js + the per-capability controllers
|
||||||
@@ -460,7 +466,8 @@ Public content GETs pass through the **siteMode** gate (§5).
|
|||||||
### /admin (admin/index.js → the capability routers in §2) — all behind `isLoggedIn` + `noindex` + `staffOnly`
|
### /admin (admin/index.js → the capability routers in §2) — all behind `isLoggedIn` + `noindex` + `staffOnly`
|
||||||
|
|
||||||
`admin/index.js` applies the shared gate and mounts each capability router at the prefix it owns;
|
`admin/index.js` applies the shared gate and mounts each capability router at the prefix it owns;
|
||||||
`users`, `invites` and `auth/providers` add `adminOnly` on top. Routes not yet extracted still live
|
`users`, `invites`, `auth/providers` and `bot-activity` add `adminOnly` on top, and `moderation` adds
|
||||||
|
`modAccess` (admin + moderator, so editors are excluded). Routes not yet extracted still live
|
||||||
in `admin.routes.js`, mounted last at the group root. The URLs below are unaffected by which file a
|
in `admin.routes.js`, mounted last at the group root. The URLs below are unaffected by which file a
|
||||||
route currently sits in — that is the property the route manifest freezes.
|
route currently sits in — that is the property the route manifest freezes.
|
||||||
| Method | Path | Purpose |
|
| Method | Path | Purpose |
|
||||||
|
|||||||
Reference in New Issue
Block a user