feat(shard): admin write plane, help-page queue, and public champion board
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:
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user