feat(theming): settings-store, nav merge util and radius tokens
Phases 0-2 of docs/website/THEMING_AND_NAV.md. Groundwork only: no admin UI, no consumer wiring, and an instance that never touches the new settings keys renders exactly as it does today. Phase 0 - settings store: - settingsDb.remove() and DELETE /api/v1/admin/settings/:key, the "reset to default" primitive. Defaults for these keys live in BRAND_* env, theme.css and the hardcoded NAV arrays, so reset has to delete the row rather than store a copy of the default. Allowlisted to the five theming/nav keys plus hero_layout_draft, admin-only, idempotent. - GET /api/v1/settings/nav behind requireAuth with no role gate. AdminLayout renders for editors and moderators and PlayerPortalLayout for players, and none of them can read GET /admin/settings, so without this their nav override would silently never apply. - A fifth router group for it: /public is anonymous, /admin/settings is adminOnly, /player is self-scoped data. This is configuration that needs a login. - parseJsonSetting() in utils/settingsJson.js. settings.value is TEXT, so every JSON key arrives as a string; malformed or wrong-shaped reads as absent, never as an error and never half-applied. - theme_visual / brand_assets / nav_public join PUBLIC_KEYS; nav_admin and nav_player deliberately do not. Phase 1 - client/src/lib/navOverrides.js, the pure merge util. Presentation only: it can set label/order/hidden and (grouped navs) group, and nothing else. It cannot introduce a `to`, cannot touch roles/feature, and hidden:false cannot un-hide anything - the existing filters run afterward, unchanged, and remain the boundary. Phase 2 - promoted 23 border-radius literals in theme.css to four tokens at today's values (14x8px, 4x999px, 4x10px, 1x12px). The 7px/6px editor chrome and the two 50% circles stay literal. --shadow-card and --panel-grad were already tokens. Tests: 16 new server tests, 20 new client tests. The route-manifest guard now also asserts /settings/** sits behind requireAuth. Swagger and both route artifacts regenerated. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -539,6 +539,33 @@ async function updateSettings(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete one settings row — the "reset to defaults" primitive.
|
||||
//
|
||||
// For the theming/nav keys, defaults live in BRAND_* env, theme.css and the
|
||||
// hardcoded NAV arrays; the *absence* of the row is what selects them
|
||||
// (docs/website/THEMING_AND_NAV.md §2). Resetting therefore has to delete, not
|
||||
// store a copy of the defaults, or the next change to a default would not reach
|
||||
// an instance that had ever pressed reset.
|
||||
//
|
||||
// The key allowlist is the point of the route: an unrestricted DELETE would let
|
||||
// a stray request drop site_mode or the uo-link config, where absence means
|
||||
// something else entirely. Deleting a key that is not set succeeds — reset is
|
||||
// idempotent and the UI should not have to know whether a row exists.
|
||||
async function deleteSetting(req, res) {
|
||||
const { key } = req.params
|
||||
if (!settings.DELETABLE_KEYS.includes(key)) {
|
||||
return res.status(400).json({ message: 'Setting is not resettable' })
|
||||
}
|
||||
try {
|
||||
await settings.remove(key)
|
||||
await activity.log({ req, action: 'settings.reset', detail: { key } })
|
||||
return res.json({ message: 'Setting reset to default' })
|
||||
} catch (err) {
|
||||
log.error('deleteSetting', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// ── Activity log ──────────────────────────────────────────────────────
|
||||
async function listActivity(req, res) {
|
||||
const limit = Math.min(Number(req.query.limit) || 50, 200)
|
||||
@@ -764,6 +791,7 @@ module.exports = {
|
||||
deleteWikiCategory,
|
||||
getSettings,
|
||||
updateSettings,
|
||||
deleteSetting,
|
||||
listActivity,
|
||||
listUsers,
|
||||
createUser,
|
||||
|
||||
Reference in New Issue
Block a user