docs(website): theming & nav phases 0-2 as built
Matches RunicGateway/website's phases 0-2 of THEMING_AND_NAV.md. BACKEND_DESIGN.md: - The new /settings router group and its one route, plus why it is a fifth group rather than a route on an existing one. - DELETE /admin/settings/:key in the admin route table, with the allowlist and why reset deletes instead of writing. - The five unseeded theming/nav keys under the settings schema: absence of the row is the "use the default" state, values are TEXT so consumers parse, and malformed reads as absent. - Route count 215 -> 225. THEMING_AND_NAV.md: - Phases 0-2 marked landed, with an "as landed" section recording the three things the design left open: where /settings/nav lives, where parseJsonSetting lives, and the exact 23-declaration radius promotion. - The nav merge util's ordering rules, settled by the implementation: an untouched item keeps its index as its sort key, an explicit order wins a tie against a coincidental index, equal explicit orders keep code order, and `group` is honored only when it names an existing section. - All four PR pairs target `edge`; the feature reaches `main` as one merge. api-route-inventory.json: resynced from server/routes.manifest.json. Picks up the two new routes plus eight that were already missing from the mirror since the Protocol 3.0 cutover (shard clilocs, market, points). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,17 @@ server/
|
||||
vendors, chars, sales, houses
|
||||
appeals.router.js (4) /player/appeals
|
||||
shard.controller.js + appeals.controller.js
|
||||
settings/ index.js owns the shared `noindex, requireAuth` gate
|
||||
(authenticated, ANY role) and the mount table.
|
||||
A fifth group, for site-wide settings that
|
||||
need a login but no particular role — /public
|
||||
is anonymous, /admin/settings is adminOnly
|
||||
while AdminLayout renders for editors and
|
||||
moderators, and /player is self-scoped data
|
||||
nav.router.js (1) /settings/nav — the nav_admin and
|
||||
nav_player overrides, read by the
|
||||
layouts that render them
|
||||
nav.controller.js
|
||||
admin/ index.js mounts the capability routers below at their
|
||||
own prefixes; owns the shared
|
||||
`noindex, isLoggedIn, staffOnly` gate and
|
||||
@@ -151,7 +162,12 @@ server/
|
||||
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
|
||||
settings.router.js (3) /admin/settings — adminOnly. The
|
||||
DELETE /:key is "reset to default"
|
||||
and carries its own key allowlist
|
||||
(theming/nav keys + the hero draft)
|
||||
so it can never drop site_mode or
|
||||
the uo-link config
|
||||
dashboard.router.js (2) GET /dashboard (staff-wide) and
|
||||
PUT /site-mode (adminOnly) — the
|
||||
two singletons owning no path
|
||||
@@ -248,6 +264,21 @@ Seeded keys: `site_mode` (default `maintenance`), `site_mode_changed_at`,
|
||||
`site_mode_changed_by`, `maintenance_message`, `status_message`, `homepage_teaser`,
|
||||
`contact_email` (=UOMysticmoon@gmail.com), `site_title`.
|
||||
|
||||
**Deliberately unseeded keys** — the theming & navigation overrides
|
||||
(`theme_visual`, `brand_assets`, `nav_public`, `nav_admin`, `nav_player`). All
|
||||
five are JSON strings, and **the absence of the row is the "use the default"
|
||||
state**: colors/fonts/radii fall back to `theme.css`, assets to `BRAND_*`, navs
|
||||
to the hardcoded `NAV` arrays. No migration writes defaults into them, because a
|
||||
stored copy of a default would stop tracking the default. Resetting one is
|
||||
therefore a `DELETE`, not a write — see `DELETABLE_KEYS` in `settings.model.js`
|
||||
and [THEMING_AND_NAV.md](THEMING_AND_NAV.md) §2.
|
||||
|
||||
Values are `TEXT`, so a JSON-valued key arrives as a **string** and every
|
||||
consumer parses it. Server side that is `utils/settingsJson.js`
|
||||
(`parseJsonSetting`), client side `parseLayout`/its callers; both treat a
|
||||
malformed or wrong-shaped value as **absent** rather than as an error, so a
|
||||
hand-edited row degrades to the default instead of rendering something broken.
|
||||
|
||||
### activity_log — append-only
|
||||
| col | type | notes |
|
||||
|---|---|---|
|
||||
@@ -602,7 +633,7 @@ are authoritative, and they answer different questions:
|
||||
|
||||
| Artifact | Source of truth for | Generated by |
|
||||
|---|---|---|
|
||||
| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs exist.** 215 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack |
|
||||
| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs exist.** 225 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack |
|
||||
| `server/swagger/swagger-output.json` — served at `/api/docs` | **What each route means.** Parameters, bodies, response codes, security. | `npm run swagger`, from `#swagger.*` annotations |
|
||||
|
||||
The split is deliberate: Swagger is annotation-derived, so an unannotated route is invisible in it and
|
||||
@@ -802,6 +833,17 @@ from the per-route **siteMode** middleware (§5), never from an auth gate.
|
||||
|
||||
Public content GETs pass through the **siteMode** gate (§5).
|
||||
|
||||
### /settings (settings/index.js → §2) — behind `requireAuth` + `noindex`, no role gate
|
||||
|
||||
Site-wide settings that need a login but no particular role. It exists because the
|
||||
other four groups each answer a different question: `/public` is anonymous,
|
||||
`/admin/settings` is `adminOnly`, and `/player` is data scoped to `req.user.id`.
|
||||
These rows are configuration that happens to need a login.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| GET | `/settings/nav` | `{ nav_admin, nav_player }` — the stored nav overrides as raw JSON strings (or `null`), for the two authenticated layouts that render them. Deliberately not public: an anonymous visitor has no use for either, and the admin nav's labels describe the shape of the admin surface. Open to **any** role because `AdminLayout` renders for editors and moderators and `PlayerPortalLayout` for players, none of whom can read `GET /admin/settings`. Presentation-only — the role/feature filters in those layouts still decide what is shown, and an override can never un-hide a gated item (see [THEMING_AND_NAV.md](THEMING_AND_NAV.md) §7) |
|
||||
|
||||
### /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;
|
||||
@@ -837,6 +879,7 @@ file a route sits in — that is the property the route manifest freezes.
|
||||
| GET | `/wiki` · GET `/wiki/:slug` | read incl. unpublished |
|
||||
| POST | `/wiki` · PUT `/wiki/:slug` · DELETE `/wiki/:slug` | manage pages |
|
||||
| GET | `/settings` · PUT `/settings` | read all / update `{key:value,...}` |
|
||||
| DELETE | `/settings/:key` | reset one setting to its default by deleting the row. Allowlisted to the keys whose default lives outside the store (`theme_visual`, `brand_assets`, `nav_public`, `nav_admin`, `nav_player`, `hero_layout_draft`) — anything else is `400`. Idempotent: resetting a key that was never set succeeds |
|
||||
| GET | `/activity?limit=&offset=` | paginated activity log |
|
||||
| GET | `/users` · POST `/users` · PUT `/users/:id` · DELETE `/users/:id` | user mgmt (can't delete self / last admin; password hashed on write) |
|
||||
| GET | `/users/:id/trusted-devices` | list a user's active trusted devices (never tokens) |
|
||||
|
||||
Reference in New Issue
Block a user