// SQL behind the Team provider — three questions core asks, answered from the // guild board and the roster Protocol 4 put there. // // Every statement reads only THIS module's tables. Core's Team tables are // core-internal (docs/website/TEAMS.md §10.3) and this module must never name // one, even though it is what fills them. // `query` is destructured from the core facade at require time, like every other // *.db.js here. The facade resolves `ctx` per call, so taking it now is safe even // though `ctx` does not exist yet when this file is first required. const { query } = require('../../core') /** ServUO's `RankDefinition.Ranks[4]` is Leader, and 4 is the top of the ladder. */ const LEADER_RANK = 4 /** * The guild board — one row per guild the shard has told us about. * * `members`/`online` here are the COUNTS `guild.update` carries; the roster is a * separate table (Protocol 4). Both are read, because a count is what the shard * asserts and a roster is what it enumerated, and they can legitimately disagree * for the moment between a membership change and the sweep that reports it. */ const listGuilds = () => query( `SELECT id, name, abbr, alliance, members, online, leader_serial, leader_name, leader_acct FROM shard_guilds ORDER BY name ASC`, ) const findGuild = (id) => query( `SELECT id, name, abbr, alliance, members, online, leader_serial, leader_name, leader_acct FROM shard_guilds WHERE id = ? LIMIT 1`, [id], ) /** * One guild's roster, with the site link and live presence folded in. * * Two LEFT JOINs, both deliberate: * * - `shard_account_links` resolves `user_id` HERE rather than in core, because * this module owns that table and a core that read it would be core naming a * module's table by name (§2.3). It is also why a freshly linked account * appears as linked on the next reconcile rather than needing core to know * anything about linking. * - `shard_online` is how a member's `online` is answered at all. The roster * frame does not carry it — the wire's member is the standard actor object * (`serial`, `name`, `player`, `acct?`, `webId?`), and the board's `online` is * a count, not a set. Presence therefore comes from the online table, which * is the same source the public "who's online" surface already uses. * * `web_id` on the roster row is preferred over the link table when present: it is * what the shard itself asserted at roster time, and the join is the fallback for * a member whose row predates their link. */ const listGuildMembers = (guildId) => query( "SELECT m.serial, m.name, m.acct, m.web_id, m.is_player, m.`rank`, m.rank_cliloc, m.rank_name, " + ` l.user_id AS linked_user_id, (o.serial IS NOT NULL) AS is_online FROM shard_guild_members m LEFT JOIN shard_account_links l ON l.account = m.acct LEFT JOIN shard_online o ON o.serial = m.serial WHERE m.guild_id = ? ORDER BY m.name ASC`, [guildId], ) /** * Every member at leader rank — rank 4, the top of ServUO's `RankDefinition.Ranks`. * * A set, not a single row, and that is the whole reason Protocol 4 grew a per-member * rank: the guild board carries one `leader_serial`, so before this the website could * only ever be told about one leader, while a UO guild routinely has several. * * A NULL rank is excluded by the comparison, which is correct — the shard withholds * the rank for a staff account rather than publishing the Leader its getter falsely * reports, and "not known" must not be read as "leads this guild". */ const listGuildLeaders = (guildId) => query( 'SELECT serial FROM shard_guild_members WHERE guild_id = ? AND `rank` >= ? ORDER BY name ASC', [guildId, LEADER_RANK], ) module.exports = { listGuilds, findGuild, listGuildMembers, listGuildLeaders, LEADER_RANK }