feat(shard): ingest Protocol 2.0 boards — guilds, governors, presence, houses
Phase 1 of the Protocol 2.0/2.1 integration: the read/ingest backend for the four
new uo-link boards, following the established champs/pages pattern (ingest → our
MariaDB + snapshot-on-reconnect + public SSE + token-free public endpoint).
- Schema: shard_guilds, shard_governors, shard_governor_terms, shard_presence;
extend shard_houses with the house.update registry columns (owner_name,
co_owners, friends, price, decay, in_registry) so the decay-transition and
registry feeds share one house row without clobbering each other.
- Ingest: route guild.update/remove, city.update, presence.online,
house.update/remove; log guild.join (real-time joins feed); region.enter is
broadcast-only. All new public kinds added to the SSE allowlist.
- Governor term history captured from day one: on every observed governor CHANGE
the open term is closed and a new one opened, idempotent so backfill/duplicate
city.update never spawn spurious terms. votes stays NULL (the feed carries only
candidate count, not tallies) — we never fabricate vote numbers.
- Client + backfill: getGuilds/getGovernors/getHouses/getPresence; snapshot each
board on every WS (re)connect, independently guarded so an empty/failed board
(e.g. no City Loyalty) never wipes another.
- Public endpoints: /shard/{guilds,governors,governors/:city/history,presence,houses}.
- Tests: ingest routing for all new kinds + governor term-capture idempotency
(15 new; full suite 179/179). Swagger regenerated.
Refs .plans/protocol2-integration.md (Phase 1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -104,9 +104,77 @@ async function getChamps(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/guilds — the current guild board. Served from our store;
|
||||
// live via guild.update / guild.remove / guild.join on the public SSE stream.
|
||||
async function getGuilds(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.listGuilds())
|
||||
} catch (err) {
|
||||
log.error('shard.getGuilds', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/governors — the current town-governor board (empty on shards
|
||||
// without City Loyalty). Live via city.update on the public SSE stream.
|
||||
async function getGovernors(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.listGovernors())
|
||||
} catch (err) {
|
||||
log.error('shard.getGovernors', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/governors/:city/history — the term ledger for one city
|
||||
// (look-back: "who were all the governors of Britain?"), newest first.
|
||||
async function getGovernorHistory(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.listGovernorHistory(req.params.city, req.query.limit))
|
||||
} catch (err) {
|
||||
log.error('shard.getGovernorHistory', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/presence — the online-population aggregate (count + per-facet
|
||||
// + per-region). Live via presence.online on the public SSE stream.
|
||||
async function getPresence(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.latestPresence())
|
||||
} catch (err) {
|
||||
log.error('shard.getPresence', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// GET /public/shard/houses — the house registry (every house we've seen via
|
||||
// house.update). Live via house.update / house.remove on the public SSE stream.
|
||||
async function getHouses(req, res) {
|
||||
try {
|
||||
return res.json(await shardState.listHouses())
|
||||
} catch (err) {
|
||||
log.error('shard.getHouses', 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, getChamps, stream }
|
||||
module.exports = {
|
||||
getStatus,
|
||||
getFeed,
|
||||
getEconomy,
|
||||
getOnline,
|
||||
getIdoc,
|
||||
getChamps,
|
||||
getGuilds,
|
||||
getGovernors,
|
||||
getGovernorHistory,
|
||||
getPresence,
|
||||
getHouses,
|
||||
stream,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user