feat(shard): admin write plane, help-page queue, and public champion board
All checks were successful
PR Checks / server-tests (pull_request) Successful in 10m20s
PR Checks / client-build (pull_request) Successful in 9m49s
PR Checks / bot-install (pull_request) Successful in 9m33s

Wire up the three uo-link sidecar surfaces that weren't integrated yet.

Champion spawns
- Ingest champ.update/champ.remove into a new shard_champs table (served from
  our own store, like online/houses); public /site/champs board with a nav link,
  live via the existing SSE feed (champ.* added to the public allowlist).

Staff write plane (admin + moderator)
- kick / ban / unban / broadcast via /admin/shard/*; actor is stamped server-side
  from the session, never the browser. Sidecar status codes mapped (403 disabled/
  protected, 404 unknown, 503/504 transient). admin.audit events are logged and
  surfaced at /admin/shard/audit.
- New admin "In-Game Ops" view (/admin/shard-ops), plus per-account Kick/Ban/Unban
  on the user-detail and character views (ShardAccountActions, self-gated to staff).

Help-page (support) queue
- Ingest page.new/updated/closed into a new shard_pages table; respond/close via
  /admin/shard/pages/*. Champ board and page queue are snapshotted from the
  sidecar's /champs and /pages on every WS (re)connect (guarded so a failed call
  never wipes local state).

Verified live end-to-end against MariaDB + the Rust sidecar + ServUO; unit tests
cover ingest routing (shardIngest.champsPages.test.js). Swagger regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
This commit is contained in:
2026-07-14 13:16:10 -05:00
parent 2dc360ca48
commit c31553aeb6
25 changed files with 2188 additions and 8 deletions

View File

@@ -100,6 +100,10 @@ function getHistory({ kind, limit = 100 } = {}) {
return call(`/history${qs ? `?${qs}` : ''}`)
}
const getEconomy = (limit = 100) => call(`/economy?limit=${encodeURIComponent(limit)}`)
// Live board / queue projections — snapshotted on WS (re)connect and served from
// our own store thereafter.
const getChamps = () => call('/champs')
const getPages = () => call('/pages')
// ── Commands ──────────────────────────────────────────────────────────────
const confirmLink = (code, websiteUserId) =>
@@ -109,6 +113,24 @@ const postTownCrier = ({ id, lines, durationSec }) =>
call('/towncrier', { method: 'POST', body: { id, lines, durationSec } })
const deleteTownCrier = (id) => call(`/towncrier/${encodeURIComponent(id)}`, { method: 'DELETE' })
// ── Staff write plane (§6) ─────────────────────────────────────────────────
// Every call carries `actor` — the website username of the staff member — set by
// the controller from the session, NEVER from the browser. The shard records it
// for attribution and echoes an admin.audit event back over the WS feed.
const adminKick = ({ actor, account, serial }) =>
call('/admin/kick', { method: 'POST', body: { actor, account, serial } })
const adminBan = ({ actor, account, serial, durationSec, reason }) =>
call('/admin/ban', { method: 'POST', body: { actor, account, serial, durationSec, reason } })
const adminUnban = ({ actor, account }) =>
call('/admin/unban', { method: 'POST', body: { actor, account } })
const adminBroadcast = ({ actor, text, hue }) =>
call('/admin/broadcast', { method: 'POST', body: { actor, text, hue } })
// ── Help-page (support) queue commands (§6) ────────────────────────────────
const respondPage = (pageId, { message, close }) =>
call(`/pages/${encodeURIComponent(pageId)}/respond`, { method: 'POST', body: { message, close } })
const closePage = (pageId) => call(`/pages/${encodeURIComponent(pageId)}/close`, { method: 'POST' })
module.exports = {
invalidateConfig,
health,
@@ -118,8 +140,16 @@ module.exports = {
getVendors,
getHistory,
getEconomy,
getChamps,
getPages,
confirmLink,
linkLookup,
postTownCrier,
deleteTownCrier,
adminKick,
adminBan,
adminUnban,
adminBroadcast,
respondPage,
closePage,
}