From a4d03bd956f864ec013daeac1cd3ba1b553013d1 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 27 Jul 2026 18:54:01 -0500 Subject: [PATCH] docs(website): record the PR 2 admin router split (moderation, bot-activity, activity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the code change in website: 18 more admin routes carved into moderation.router.js (15), botActivity.router.js (2) and activity.router.js (1), leaving 64 in the residual admin.routes.js. API_V2_PLAN.md gains a "PR 2 — as landed" section recording the four zero-diff gates and two decisions worth carrying into PRs 3-5: - /activity gets its own file rather than the target tree's plan to park it as a singleton inside dashboard.router.js — honouring the tree would have left one route in the residual file for two PRs, and it is a genuinely separate capability (the staff audit log, not the dashboard's stats overview and not the botScore middleware's ban state). PR 4 therefore mounts dashboard and site-mode only; the target tree is updated to match. - A gate moves to a router-level `use` only where it was already a *prefix* mount (moderation's modAccess). Bot-activity's per-route adminOnly stays per-route, because the per-route handler count is the only thing in routes.guards.json that would catch a dropped gate — requireRole(...) returns an anonymous arrow and never appears by name. BACKEND_DESIGN.md §2 (folder structure) and §4 (the /admin contract preamble) are updated for the new files and their gates. PROJECT_TREE.md is left alone — since website#98 it is auto-generated by the sync-project-tree workflow. Co-Authored-By: Claude --- website/API_V2_PLAN.md | 43 +++++++++++++++++++++++++++++++++++---- website/BACKEND_DESIGN.md | 17 +++++++++++----- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/website/API_V2_PLAN.md b/website/API_V2_PLAN.md index 76eca46..02c5323 100644 --- a/website/API_V2_PLAN.md +++ b/website/API_V2_PLAN.md @@ -1,6 +1,6 @@ # 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` > **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 uploads.router.js shard.router.js uoLink.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/ 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) @@ -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. +### 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) 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 as a `report-to` group on the enforced policy. 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`. 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). Each PR: **zero-line diff in `routes.manifest.json`**, server tests green diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 724ba2a..8680808 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -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 > [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 -> their own routers under `admin/`, behind a new `admin/index.js`. The remaining 82 admin routes are -> still in `admin.routes.js`, and `public/` and `player/` are untouched. +> **Landed so far:** admin `users`, `account`, `invites`, `auth/providers` (PR 1, 28 routes) and +> `moderation`, `bot-activity`, `activity` (PR 2, 18 routes) now live in their own routers under +> `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` > (`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 invites.router.js (3) /admin/invites — 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 when the final split PR lands 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/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 route currently sits in — that is the property the route manifest freezes. | Method | Path | Purpose | -- 2.49.1