diff --git a/website/API_V2_PLAN.md b/website/API_V2_PLAN.md index 7a63567..3a9cca5 100644 --- a/website/API_V2_PLAN.md +++ b/website/API_V2_PLAN.md @@ -421,6 +421,63 @@ Notes: capabilities that read alike** — the latter stays with `shard` in PR 4. Same trap as PR 2's `activity` / `dashboard` / `bot-activity` trio. +### PR 4 — as landed + +`shard`, `uo-link`, `email`, `discord-bot`, `settings` and `dashboard`/`site-mode` — the whole residual +33. **`admin.routes.js` is deleted**, so the admin group is fully split and every one of its 110 routes +is declared in a capability router. + +| Router | Routes | Prefix | Extra gate | +|---|---|---|---| +| `shard.router.js` | 16 | `/admin/shard` | none on the 7 self-service routes; `modAccess` per route on the 9 staff ops | +| `uoLink.router.js` | 5 | `/admin/uo-link` | `adminOnly` per route | +| `email.router.js` | 6 | `/admin/email` | `adminOnly` per route | +| `discordBot.router.js` | 2 | `/admin/discord-bot` | `adminOnly` per route | +| `settings.router.js` | 2 | `/admin/settings` | `adminOnly` per route | +| `dashboard.router.js` | 2 | **group root** (`/dashboard`, `/site-mode`) | `adminOnly` per route on `/site-mode` only | +| ~~`admin.routes.js`~~ | — | deleted | — | + +16 + 5 + 6 + 2 + 2 + 2 = 33. 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 server tests green. + +Notes: + +- **`dashboard.router.js` is mounted at the group root, not a prefix — the one relaxation of the + "always mount at a prefix" rule, and it is deliberate.** `GET /dashboard` and `PUT /site-mode` own no + common path segment, so a prefix mount would mean two one-route files instead of the single file the + target tree calls for. It is safe **only** because the file declares no router-level middleware: a + bare `use(gate)` in a root-mounted router runs for every request passing through toward another + mount and would 403 an editor on an unrelated route (the PR 1 finding). The file says so in a + comment, because the next person to add a gate there is the one who needs to know. +- **`/shard` is the first prefix where two tiers share one router**, and it is why prefix ownership + beats swagger-tag grouping. The 7 self-service routes (`link`, `accounts`, `roster/:account`, + `vendors/:account`, `char/:serial`, `sales`, `POST account`) are tagged `Admin · Account`, run with + no gate beyond the shared `staffOnly`, and are served by the very same `player/shard.controller` + handlers as `/player/shard` — staff are a superset of players, and the controller keys off + `req.user.id`. The 9 in-game ops are tagged `Admin · Shard` and carry `modAccess`. Splitting them by + tag would put two routers under one prefix for no gain; instead one router owns `/shard` and gates + per route. The tag mismatch stays, on the PR 1 and PR 3 precedent: retagging is a real spec diff and + belongs in a PR that is about tags. +- **No gate moved to router level anywhere in this PR.** Every `adminOnly` in the residual file was + per-route, and `modAccess` on `/shard` must stay per-route because half that router must *not* have + it. This keeps the per-route handler count intact — the one number `routes.guards.json` can actually + check, since `requireRole(...)` returns an anonymous arrow. +- **The `/:param` shadowing check was run again and is clean**, since the manifest sorts and therefore + cannot see declaration order. Introspecting the built stack, all 110 admin routes and all 59 literal + admin paths dispatch to their own layer — nothing is captured first by a `:param` sibling. The + near-misses worth naming: `GET /shard/pages` (help-page queue) sits alongside + `POST /shard/pages/:id/respond|close`, and `POST /shard/towncrier` alongside + `DELETE /uo-link/towncrier/:id` — different depths and methods, so neither collides. +- **Deleting the file left dangling `see admin.routes.js` pointers**, which were repointed in the same + PR: `botActivity.controller.js` → `botActivity.router.js`, `moderation.controller.js` → + `moderation.router.js`, `announceJobs.logic.js`'s town-crier cap mirror → `admin/uoLink.router.js`, + and the "route paths sit on the line *after* `adminRouter.get(`" rationale in + `scripts/routeManifest.js`, `README.md` and `pr-checks.yml` was generalized (it was never about that + one file). +- **`/admin/shard/pages` vs `/admin/pages` stayed separate**, as PR 3 flagged: the former is the + in-game help-page (support) queue and belongs to `shard`; the latter is the CMS page builder. + ### 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 @@ -533,8 +590,8 @@ deliberate `+1` in the manifest — which is exactly the mechanism working as de 6. **PR 3 — admin (content):** `posts`, `pages`, `wiki`, `uploads`. ✅ landed 7. **PR 4 — admin (ops/config):** `shard`, `uo-link`, `email`, `discord-bot`, `settings`, `site-mode`, `dashboard`. (`activity` went with PR 2 — see § PR 2 — as landed.) **This is the whole residual - file** — `admin.routes.js` is deleted here, not by PR 5. -8. **PR 5 — `public/*` + `player/*`** (and the residual `auth/*` grouping). + file** — `admin.routes.js` is deleted here, not by PR 5. ✅ landed +8. **PR 5 — `public/*` + `player/*`** (and the residual `auth/*` grouping). **The only split PR left.** Each PR: **zero-line diff in `routes.manifest.json`**, server tests green (`cd website/server && npm test`), Swagger regenerated, matching `docs/` edit, Conventional Commit, diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 28f9302..92b3cc9 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -34,10 +34,12 @@ 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`, `auth/providers` (PR 1, 28 routes), -> `moderation`, `bot-activity`, `activity` (PR 2, 18 routes) and `posts`, `uploads`, `wiki`, `pages` -> (PR 3, 31 routes) now live in their own routers under `admin/`, behind a new `admin/index.js`. The -> remaining 33 admin routes are still in `admin.routes.js`, and `public/` and `player/` are untouched. +> **Landed so far:** the **admin group is fully split**. `users`, `account`, `invites`, +> `auth/providers` (PR 1, 28 routes), `moderation`, `bot-activity`, `activity` (PR 2, 18 routes), +> `posts`, `uploads`, `wiki`, `pages` (PR 3, 31 routes) and `shard`, `uo-link`, `email`, +> `discord-bot`, `settings`, `dashboard`/`site-mode` (PR 4, 33 routes) each live in their own router +> under `admin/`, behind `admin/index.js` — **`admin.routes.js` is deleted**. `public/` and `player/` +> are untouched and are the subject of the last split PR. > > "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 @@ -83,9 +85,24 @@ server/ pages.router.js (7) /admin/pages — CMS page builder imageUpload.js shared multer config for the two upload routes above (not a router) - admin.routes.js (33) everything not yet split, mounted - last at the group root; goes away - when the final split PR lands + shard.router.js (16) /admin/shard — 7 self-service + account-linking routes (no extra + gate, handlers shared with + /player/shard) + 9 in-game staff + ops on modAccess, per route + uoLink.router.js (5) /admin/uo-link — sidecar config, + town crier, admin SSE — adminOnly + email.router.js (6) /admin/email — Gmail OAuth2 + delivery — adminOnly + discordBot.router.js (2) /admin/discord-bot — adminOnly + settings.router.js (2) /admin/settings — adminOnly + dashboard.router.js (2) GET /dashboard (staff-wide) and + PUT /site-mode (adminOnly) — the + two singletons owning no path + segment, so mounted at the group + root; declares no router-level + middleware, which is what makes a + root mount safe admin.controller.js + the per-capability controllers (already domain-split; the split PRs re-wire routes, not logic) @@ -478,9 +495,11 @@ Public content GETs pass through the **siteMode** gate (§5). `users`, `invites`, `auth/providers` and `bot-activity` add `adminOnly` on top, and `moderation` adds `modAccess` (admin + moderator, so editors are excluded). The content capabilities — `posts`, `uploads`, `wiki`, `pages` — add nothing: managing content is the editor tier's job, so `staffOnly` is -the whole gate. 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. +the whole gate. The ops/config capabilities — `uo-link`, `email`, `discord-bot`, `settings`, and +`PUT /site-mode` — are `adminOnly`; `shard` is the one mixed prefix, where self-service account +linking carries no extra gate and the in-game staff operations carry `modAccess`. There is no residual +file: every admin route is declared in a capability router. The URLs below are unaffected by which +file a route sits in — that is the property the route manifest freezes. | Method | Path | Purpose | |---|---|---| | GET | `/dashboard` | current mode, last change time + who, content counts, recent activity | diff --git a/website/WIKI_UPGRADE.md b/website/WIKI_UPGRADE.md index 91d55f1..283eb14 100644 --- a/website/WIKI_UPGRADE.md +++ b/website/WIKI_UPGRADE.md @@ -54,7 +54,7 @@ auth model (admin/editor — no new roles, no public contributions). | Schema | flat `wiki_pages(slug,title,body,updated_by,timestamps)` | [server/db/schema.sql:31](server/db/schema.sql) | | Model | thin CRUD by slug | [server/src/model/wiki/wiki.db.js](server/src/model/wiki/wiki.db.js), [wiki.model.js](server/src/model/wiki/wiki.model.js) | | Public API | `GET /public/wiki`, `GET /public/wiki/:slug` | [public.controller.js:53](server/src/router/v1/public/public.controller.js) | -| Admin API | `GET/POST/PUT/DELETE /admin/wiki[...]` | [admin.controller.js:163](server/src/router/v1/admin/admin.controller.js), [admin.routes.js:68](server/src/router/v1/admin/admin.routes.js) | +| Admin API | `GET/POST/PUT/DELETE /admin/wiki[...]` | [admin.controller.js:163](server/src/router/v1/admin/admin.controller.js), [wiki.router.js](server/src/router/v1/admin/wiki.router.js) | | Public UI | card grid (hardcoded blurbs + Roman numerals), article w/ auto-TOC | [Wiki.jsx](client/src/routes/wiki/Wiki.jsx), [WikiArticle.jsx](client/src/routes/wiki/WikiArticle.jsx) | | Admin UI | raw-HTML `