docs(website): record the PR 4 admin router split and the end of admin.routes.js #58
@@ -421,6 +421,63 @@ Notes:
|
|||||||
capabilities that read alike** — the latter stays with `shard` in PR 4. Same trap as PR 2's
|
capabilities that read alike** — the latter stays with `shard` in PR 4. Same trap as PR 2's
|
||||||
`activity` / `dashboard` / `bot-activity` trio.
|
`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)
|
### 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
|
||||||
@@ -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
|
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`,
|
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
|
`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.
|
file** — `admin.routes.js` is deleted here, not by PR 5. ✅ landed
|
||||||
8. **PR 5 — `public/*` + `player/*`** (and the residual `auth/*` grouping).
|
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
|
Each PR: **zero-line diff in `routes.manifest.json`**, server tests green
|
||||||
(`cd website/server && npm test`), Swagger regenerated, matching `docs/` edit, Conventional Commit,
|
(`cd website/server && npm test`), Swagger regenerated, matching `docs/` edit, Conventional Commit,
|
||||||
|
|||||||
@@ -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
|
> 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`, `auth/providers` (PR 1, 28 routes),
|
> **Landed so far:** the **admin group is fully split**. `users`, `account`, `invites`,
|
||||||
> `moderation`, `bot-activity`, `activity` (PR 2, 18 routes) and `posts`, `uploads`, `wiki`, `pages`
|
> `auth/providers` (PR 1, 28 routes), `moderation`, `bot-activity`, `activity` (PR 2, 18 routes),
|
||||||
> (PR 3, 31 routes) now live in their own routers under `admin/`, behind a new `admin/index.js`. The
|
> `posts`, `uploads`, `wiki`, `pages` (PR 3, 31 routes) and `shard`, `uo-link`, `email`,
|
||||||
> remaining 33 admin routes are still in `admin.routes.js`, and `public/` and `player/` are untouched.
|
> `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`
|
> "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
|
||||||
@@ -83,9 +85,24 @@ server/
|
|||||||
pages.router.js (7) /admin/pages — CMS page builder
|
pages.router.js (7) /admin/pages — CMS page builder
|
||||||
imageUpload.js shared multer config for the two
|
imageUpload.js shared multer config for the two
|
||||||
upload routes above (not a router)
|
upload routes above (not a router)
|
||||||
admin.routes.js (33) everything not yet split, mounted
|
shard.router.js (16) /admin/shard — 7 self-service
|
||||||
last at the group root; goes away
|
account-linking routes (no extra
|
||||||
when the final split PR lands
|
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
|
admin.controller.js + the per-capability controllers
|
||||||
(already domain-split; the split PRs re-wire
|
(already domain-split; the split PRs re-wire
|
||||||
routes, not logic)
|
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
|
`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`,
|
`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
|
`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
|
the whole gate. The ops/config capabilities — `uo-link`, `email`, `discord-bot`, `settings`, and
|
||||||
root. The URLs below are unaffected by which file a route currently sits in — that is the property the
|
`PUT /site-mode` — are `adminOnly`; `shard` is the one mixed prefix, where self-service account
|
||||||
route manifest freezes.
|
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 |
|
| Method | Path | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | `/dashboard` | current mode, last change time + who, content counts, recent activity |
|
| GET | `/dashboard` | current mode, last change time + who, content counts, recent activity |
|
||||||
|
|||||||
@@ -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) |
|
| 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) |
|
| 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) |
|
| 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) |
|
| 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 `<textarea>` modal | [WikiAdmin.jsx](client/src/routes/admin/views/WikiAdmin.jsx), [WikiEditor.jsx](client/src/routes/admin/views/WikiEditor.jsx) |
|
| Admin UI | raw-HTML `<textarea>` modal | [WikiAdmin.jsx](client/src/routes/admin/views/WikiAdmin.jsx), [WikiEditor.jsx](client/src/routes/admin/views/WikiEditor.jsx) |
|
||||||
| API client | `api.wiki`, `api.admin.*Wiki` | [client/src/api/client.js:52](client/src/api/client.js) |
|
| API client | `api.wiki`, `api.admin.*Wiki` | [client/src/api/client.js:52](client/src/api/client.js) |
|
||||||
@@ -196,7 +196,7 @@ centralized error handler unchanged. **Every write logs to `activity_log`**
|
|||||||
|
|
||||||
### 4.4 Image uploads
|
### 4.4 Image uploads
|
||||||
|
|
||||||
Generalize the existing screenshot upload (multer config in [admin.routes.js:17](server/src/router/v1/admin/admin.routes.js))
|
Generalize the existing screenshot upload (multer config now in [admin/imageUpload.js](server/src/router/v1/admin/imageUpload.js))
|
||||||
into a shared `POST /admin/uploads` returning `{ url: "/uploads/<file>" }`, reused by both
|
into a shared `POST /admin/uploads` returning `{ url: "/uploads/<file>" }`, reused by both
|
||||||
the post editor and the wiki editor. Same size/mime limits. No new storage —
|
the post editor and the wiki editor. Same size/mime limits. No new storage —
|
||||||
served from the existing `uploads/` volume.
|
served from the existing `uploads/` volume.
|
||||||
|
|||||||
Reference in New Issue
Block a user