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:
@@ -1492,6 +1492,165 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/guilds": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Public · Shard"
|
||||
],
|
||||
"summary": "Current guild board (rosters, alliances, leaders)",
|
||||
"description": "The live board of every guild. Update in place via the guild.update / guild.remove / guild.join frames on /shard/stream.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Guilds, ordered by name",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/governors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Public · Shard"
|
||||
],
|
||||
"summary": "Current town-governor board (City Loyalty)",
|
||||
"description": "One entry per city with its governor and election phase. Empty if the shard does not run the City Loyalty system. Live via city.update on /shard/stream.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Cities, ordered by name",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/governors/{city}/history": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Public · Shard"
|
||||
],
|
||||
"summary": "Governor term history for a city",
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "city",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "City name, e.g. Britain."
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Max terms (default 100, max 500)."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Terms, newest first",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/presence": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Public · Shard"
|
||||
],
|
||||
"summary": "Online population aggregate (count + per-facet + per-region)",
|
||||
"description": "The latest presence.online snapshot powering the \"Players Online\" widget. Live via presence.online on /shard/stream.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Population snapshot",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/houses": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Public · Shard"
|
||||
],
|
||||
"summary": "House registry (owner, co-owners, price, decay)",
|
||||
"description": "Every house seen via the house.update registry feed. `price` is the placement value, not a for-sale flag. Live via house.update / house.remove on /shard/stream.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Houses, ordered by name",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ShardHouse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/public/shard/stream": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
Reference in New Issue
Block a user