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:
@@ -176,6 +176,14 @@ publicRouter.get(
|
||||
/* #swagger.responses[200] = { description: 'IDOC houses', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/ShardHouse" } } } } } */
|
||||
shard.getIdoc,
|
||||
)
|
||||
publicRouter.get(
|
||||
'/shard/champs',
|
||||
// #swagger.tags = ['Public · Shard']
|
||||
// #swagger.summary = 'Current champion-spawn board (all categories)'
|
||||
// #swagger.description = 'The live board of every champion / mini-champ / sea-boss spawn. Update in place via the champ.update / champ.remove frames on /shard/stream.'
|
||||
/* #swagger.responses[200] = { description: 'Champion spawns, ordered by name', content: { "application/json": { schema: { type: "array", items: { type: "object", additionalProperties: true } } } } } */
|
||||
shard.getChamps,
|
||||
)
|
||||
publicRouter.get(
|
||||
'/shard/stream',
|
||||
// #swagger.tags = ['Public · Shard']
|
||||
|
||||
@@ -92,9 +92,21 @@ async function getIdoc(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/champs — the current champion-spawn board (all categories).
|
||||
// Served from our own store; live deltas (champ.update / champ.remove) arrive on
|
||||
// the public SSE stream so the page can update in place.
|
||||
async function getChamps(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.listChamps())
|
||||
} catch (err) {
|
||||
log.error('shard.getChamps', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/stream — public live-event SSE channel (safe kinds only).
|
||||
function stream(req, res) {
|
||||
broadcast.subscribe(req, res, 'public')
|
||||
}
|
||||
|
||||
module.exports = { getStatus, getFeed, getEconomy, getOnline, getIdoc, stream }
|
||||
module.exports = { getStatus, getFeed, getEconomy, getOnline, getIdoc, getChamps, stream }
|
||||
|
||||
Reference in New Issue
Block a user