diff --git a/website/API_V2_PLAN.md b/website/API_V2_PLAN.md index 02c5323..7a63567 100644 --- a/website/API_V2_PLAN.md +++ b/website/API_V2_PLAN.md @@ -376,6 +376,51 @@ Notes: - **`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. +### PR 3 — as landed + +`posts`, `uploads`, `wiki` and `pages` — 31 routes, leaving 33 in the residual file. The content tier, +and the first split PR where no gate moved at all: all four capabilities are editor-tier, so the shared +`staffOnly` in `admin/index.js` is their whole gate. + +| Router | Routes | Prefix | Extra gate | +|---|---|---|---| +| `posts.router.js` | 9 | `/admin/posts` | none — editor tier | +| `uploads.router.js` | 1 | `/admin/uploads` | none — editor tier | +| `wiki.router.js` | 14 | `/admin/wiki` | none — editor tier | +| `pages.router.js` | 7 | `/admin/pages` | none — editor tier | +| `admin.routes.js` (residual) | 33 | group root, mounted last | unchanged | + +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: + +- **The residual 33 is exactly PR 4's list** — `shard` 16, `email` 6, `uo-link` 5, `settings` 2, + `discord-bot` 2, `dashboard` 1, `site-mode` 1. So `admin.routes.js` is deleted by PR 4, one PR + earlier than the sequencing list implies, and PR 5 touches only `public/*`, `player/*` and `auth/*`. +- **A shared module was unavoidable here, and it is the first one in the split.** The multer config + (upload dir, mimetype→extension allowlist, 8 MB cap) was defined inline in `admin.routes.js` and used + by *two* routes that this PR puts in different files: `POST /posts/upload` (→ `{image_url}`) and + `POST /uploads` (→ `{url}`). It moved to `admin/imageUpload.js` rather than being duplicated — + duplicating a security allowlist is how the two copies drift. It stays in `admin/` deliberately: + `UPLOAD_DIR` is resolved `__dirname`-relative, so relocating the file would silently repoint the + upload directory. Guard freshness is unaffected — multer's middleware is named `multerMiddleware` + wherever it is constructed, so `routes.guards.json` did not move. +- **`POST /uploads` keeps its `Admin · Posts` swagger tag**, which now disagrees with its filename. The + acceptance criterion is a byte-identical spec, so retagging is a real OpenAPI diff and does not + belong in a route-move PR. Same call as PR 1's `/shard/*` tag mismatch: fix tags in a PR that is + *about* tags. +- **The wiki router is the first one with load-bearing intra-file route order.** `/categories` and + `/tags` are literal paths that must stay ahead of `/:slug`, or `GET /admin/wiki/categories` gets + dispatched as a page whose slug is "categories". **The manifest cannot catch this — it sorts its + entries, so a reordering is invisible in all three gates.** It was verified separately by + introspecting the built router stack and asserting the last literal layer precedes the first `/:slug` + layer. Any future PR moving `/:slug`-style routes needs the same explicit check. +- **`/admin/pages` (CMS page builder) and `/admin/shard/pages` (in-game help-page queue) are unrelated + capabilities that read alike** — the latter stays with `shard` in PR 4. Same trap as PR 2's + `activity` / `dashboard` / `bot-activity` trio. + ### 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 @@ -485,9 +530,10 @@ deliberate `+1` in the manifest — which is exactly the mechanism working as de 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`. ✅ landed -6. **PR 3 — admin (content):** `posts`, `pages`, `wiki`, `uploads`. +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.) + `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). 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 8680808..28f9302 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -34,10 +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`, `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. +> **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. > > "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 @@ -74,7 +74,16 @@ server/ 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 + posts.router.js (9) /admin/posts — editor tier, no + gate beyond staffOnly + uploads.router.js (1) /admin/uploads — rich-text editor + image upload + wiki.router.js (14) /admin/wiki — pages, revisions, + categories, tags + 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 admin.controller.js + the per-capability controllers @@ -467,9 +476,11 @@ Public content GETs pass through the **siteMode** gate (§5). `admin/index.js` applies the shared gate and mounts each capability router at the prefix it owns; `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. +`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. | Method | Path | Purpose | |---|---|---| | GET | `/dashboard` | current mode, last change time + who, content counts, recent activity |