feat(shard): Protocol 2.0 cross-links — titles, guild, governor, houses
Phase 3: surface the new board data on existing character/user pages. - Character sheet: render the char.profile titles block (fame/karma + skill + selected reward title; numeric clilocs skipped since the site has no cliloc table yet), plus "Guildmaster" and "Governor of <city>" chips. - Char profile enrichment (player/admin /shard/char/:serial, one shared path): attach guild + governorOf from our own boards. Guild is LEADERSHIP-ONLY — it's verifiable from current board state, whereas guessing membership from stale guild.join events risks showing a wrong guild, so we return null instead. - Admin user detail (/admin/users/:id): new "Standing" section (governorships held + guilds led) via GET /users/:id/shard/standing; Houses rows now show the registry fields (decay level, placement price, co-owner/friend counts) already returned by listHousesForAccounts. Server 179/179, client build clean, swagger regenerated. Refs .plans/protocol2-integration.md (Phase 3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -189,6 +189,28 @@ const removeGuild = (id) => query('DELETE FROM shard_guilds WHERE id = ?', [id])
|
||||
const clearGuilds = () => query('DELETE FROM shard_guilds')
|
||||
const listGuilds = () => query(`SELECT ${GUILD_COLS} FROM shard_guilds ORDER BY name ASC`)
|
||||
|
||||
// The guild an actor LEADS — matched on the current board (leader_serial or the
|
||||
// linked leader_acct), so it reflects live state. Guild MEMBERSHIP for non-leaders
|
||||
// is not modelled (the board carries only counts + leader), so we don't guess it.
|
||||
const findGuildLedByActor = (serial, acct) =>
|
||||
query(
|
||||
`SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
|
||||
WHERE leader_serial = ? OR (leader_acct IS NOT NULL AND leader_acct = ?)
|
||||
LIMIT 1`,
|
||||
[serial ?? null, acct ?? null],
|
||||
)
|
||||
|
||||
// Guilds led by any of the given game accounts (admin: a user's linked accounts).
|
||||
const listGuildsLedByAccounts = (accounts) =>
|
||||
accounts.length === 0
|
||||
? Promise.resolve([])
|
||||
: query(
|
||||
`SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
|
||||
WHERE leader_acct IN (${accounts.map(() => '?').join(', ')})
|
||||
ORDER BY name ASC`,
|
||||
accounts,
|
||||
)
|
||||
|
||||
// ── Governor board + term history (Protocol 2.0) ───────────────────────────
|
||||
const GOV_COLS =
|
||||
'city, governor_serial, governor_name, governor_acct, governor_web_id, elect_serial, elect_name, elect_acct, election_phase, candidates, auto_pick_at, payload, t, updated_at'
|
||||
@@ -286,6 +308,8 @@ module.exports = {
|
||||
removeGuild,
|
||||
clearGuilds,
|
||||
listGuilds,
|
||||
findGuildLedByActor,
|
||||
listGuildsLedByAccounts,
|
||||
upsertGovernor,
|
||||
listGovernors,
|
||||
listGovernorshipsByAccounts,
|
||||
|
||||
@@ -382,6 +382,22 @@ async function replaceGuilds(guilds) {
|
||||
for (const ev of guilds || []) await upsertGuild(ev)
|
||||
}
|
||||
|
||||
// The guild an actor leads (cross-link on the character sheet). Leadership only —
|
||||
// see the db note; membership for rank-and-file isn't in the feed, so we return
|
||||
// null rather than show a possibly-stale guess.
|
||||
async function findGuildForActor({ serial, acct }) {
|
||||
const rows = await db.findGuildLedByActor(serial ?? null, acct ?? null)
|
||||
const g = rows[0]
|
||||
if (!g) return null
|
||||
return { id: g.id, name: g.name, abbr: g.abbr, alliance: g.alliance, role: 'leader' }
|
||||
}
|
||||
|
||||
// Guilds led by any of a user's linked accounts (admin user-detail cross-link).
|
||||
async function listGuildsLedForAccounts(accounts) {
|
||||
const rows = await db.listGuildsLedByAccounts(accounts)
|
||||
return rows.map((g) => ({ id: g.id, name: g.name, abbr: g.abbr, alliance: g.alliance, leaderName: g.leader_name }))
|
||||
}
|
||||
|
||||
// ── Town governors (Protocol 2.0) ──────────────────────────────────────────
|
||||
// Upsert a city's governance snapshot (city.update) AND capture term history.
|
||||
// Term capture runs first (it reads the CURRENT open term to decide whether the
|
||||
@@ -543,6 +559,8 @@ module.exports = {
|
||||
clearGuilds,
|
||||
listGuilds,
|
||||
replaceGuilds,
|
||||
findGuildForActor,
|
||||
listGuildsLedForAccounts,
|
||||
upsertGovernor,
|
||||
listGovernors,
|
||||
listGovernorshipsForAccounts,
|
||||
|
||||
Reference in New Issue
Block a user