const { query } = require('../../utils/db') // ── Online players ───────────────────────────────────────────────────────── const ONLINE_COLS = 'serial, name, acct, web_id, map, x, y, z, hits, hits_max, mana, mana_max, stam, stam_max, str, dex, `int`, updated_at' // Upsert one online player. `fields` already prepared by the model (only the // columns it wants to write); serial is required and is the primary key. async function upsertOnline(serial, fields) { const cols = Object.keys(fields) const allCols = ['serial', ...cols] const insertCols = allCols.map((c) => `\`${c}\``).join(', ') const placeholders = allCols.map(() => '?').join(', ') // Never overwrite an existing column with NULL on refresh (a char.vitals frame // that omits acct/name shouldn't blank what mob.login set) — COALESCE keeps the // prior value when the incoming one is NULL. const updates = cols.map((c) => `\`${c}\` = COALESCE(VALUES(\`${c}\`), \`${c}\`)`).join(', ') await query( `INSERT INTO shard_online (${insertCols}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}`, [serial, ...cols.map((c) => fields[c])], ) } const removeOnline = (serial) => query('DELETE FROM shard_online WHERE serial = ?', [serial]) const clearOnline = () => query('DELETE FROM shard_online') async function countOnline() { const rows = await query('SELECT COUNT(*) AS n FROM shard_online') return rows[0] ? Number(rows[0].n) : 0 } const listOnline = () => query(`SELECT ${ONLINE_COLS} FROM shard_online ORDER BY name ASC`) // Online players on any of the given game accounts (admin: a user's linked // accounts). Empty list short-circuits so we never emit `IN ()`. const listOnlineByAccounts = (accounts) => accounts.length === 0 ? Promise.resolve([]) : query( `SELECT ${ONLINE_COLS} FROM shard_online WHERE acct IN (${accounts.map(() => '?').join(', ')}) ORDER BY name ASC`, accounts, ) // Staff roles whose online presence is shown on the public Shard page. Players // who link an account are NOT surfaced publicly — only staff opt into visibility // by virtue of being staff. const PUBLIC_ONLINE_ROLES = ['admin', 'editor', 'moderator'] // Online players whose game account is linked to a STAFF website user. Joined // against shard_account_links (not the sidecar-supplied web_id) so a link takes // effect immediately, regardless of whether the player has re-logged since // linking, then through to users so only staff roles are surfaced publicly. const listOnlineLinked = () => query( `SELECT ${ONLINE_COLS.split(', ').map((c) => `o.${c}`).join(', ')} FROM shard_online o JOIN shard_account_links l ON l.account = o.acct JOIN users u ON u.id = l.user_id WHERE u.role IN (${PUBLIC_ONLINE_ROLES.map(() => '?').join(', ')}) ORDER BY o.name ASC`, PUBLIC_ONLINE_ROLES, ) // ── Economy supply series ──────────────────────────────────────────────── const insertEconomy = ({ accounts, gold, t }) => query('INSERT INTO shard_economy (accounts, gold, t) VALUES (?, ?, ?)', [ accounts ?? null, gold ?? null, t, ]) const listEconomy = (limit) => query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT ?', [limit]) async function latestEconomy() { const rows = await query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT 1') return rows[0] || null } // ── Houses / IDOC ──────────────────────────────────────────────────────── const HOUSE_COLS = 'serial, stage, map, x, y, z, region, name, owner_serial, owner_acct, built_on, last_refreshed, is_idoc, updated_at' async function upsertHouse(serial, fields) { const cols = Object.keys(fields) const allCols = ['serial', ...cols] const insertCols = allCols.map((c) => `\`${c}\``).join(', ') const placeholders = allCols.map(() => '?').join(', ') const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ') await query( `INSERT INTO shard_houses (${insertCols}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}`, [serial, ...cols.map((c) => fields[c])], ) } const listIdocHouses = () => query(`SELECT ${HOUSE_COLS} FROM shard_houses WHERE is_idoc = 1 ORDER BY updated_at DESC`) // Houses owned by any of the given game accounts (admin: a user's linked // accounts). IDOC houses first, then newest-refreshed. Empty list short-circuits. const listHousesByAccounts = (accounts) => accounts.length === 0 ? Promise.resolve([]) : query( `SELECT ${HOUSE_COLS} FROM shard_houses WHERE owner_acct IN (${accounts.map(() => '?').join(', ')}) ORDER BY is_idoc DESC, updated_at DESC`, accounts, ) module.exports = { upsertOnline, removeOnline, clearOnline, countOnline, listOnline, listOnlineLinked, listOnlineByAccounts, insertEconomy, listEconomy, latestEconomy, upsertHouse, listIdocHouses, listHousesByAccounts, }