// SQL for the integration bridge's configuration (TEAMS.md §7.2, phase 8). // // One table, and almost all of its subtlety is in the schema comment rather than // here: `team_key` is a generated `IFNULL(team_id, 0)`, so the deployment-wide // default and the per-Team overrides live under one UNIQUE key without the // default row needing a NULL in a primary key it cannot have. // // **Reads join `teams` and callers get the Team's name.** Not for display alone: // the resolver's answer is the input to a message that names a Team, and a second // round trip per notification to fetch a name the first query already walked past // is the kind of thing that only shows up under a busy forum. const { query } = require('../../utils/db') const COLUMNS = ` c.id, c.platform, c.team_id, c.events, c.channel_ref, c.enabled, c.members_ack, c.members_ack_by, c.members_ack_at, c.updated_at` /** * Every row for a platform — the default first, then the overrides by Team name. * * The admin panel's whole listing, in one query. `team_name` is NULL on exactly * one row (the default), which is also how the client tells them apart without * needing to reason about `team_id`. */ async function listForPlatform(platform) { return query( `SELECT ${COLUMNS}, t.name AS team_name, t.slug AS team_slug, t.display_name_override, u.username AS members_ack_username FROM team_integration_config c LEFT JOIN teams t ON t.id = c.team_id LEFT JOIN users u ON u.id = c.members_ack_by WHERE c.platform = ? ORDER BY c.team_id IS NOT NULL, COALESCE(t.name, '')`, [platform], ) } /** * The row that governs `teamId`, or null. * * `team_key` is what makes this one query rather than two: asking for the pair * (0, teamId) returns the default and the override together, and `ORDER BY * team_key DESC LIMIT 1` puts the override first when it exists. A caller that * fetched the default and then looked for an override would do two round trips * per notification for an answer the index already holds. */ async function resolveFor(platform, teamId) { const rows = await query( `SELECT ${COLUMNS}, t.name AS team_name, t.display_name_override FROM team_integration_config c LEFT JOIN teams t ON t.id = c.team_id WHERE c.platform = ? AND c.team_key IN (0, ?) ORDER BY c.team_key DESC LIMIT 1`, [platform, Number(teamId)], ) return rows[0] || null } async function getById(id) { const rows = await query( `SELECT ${COLUMNS}, t.name AS team_name FROM team_integration_config c LEFT JOIN teams t ON t.id = c.team_id WHERE c.id = ? LIMIT 1`, [Number(id)], ) return rows[0] || null } async function getForTeam(platform, teamId) { const rows = await query( `SELECT ${COLUMNS} FROM team_integration_config c WHERE c.platform = ? AND c.team_key = ? LIMIT 1`, [platform, teamId === null || teamId === undefined ? 0 : Number(teamId)], ) return rows[0] || null } /** * Create or replace the row for (platform, team). * * A full replace rather than a patch, and the acknowledgement columns are part of * what is replaced — the model decides what they should be, because "did the * channel change" is a comparison against the row that is about to be overwritten * and only the model has both halves. */ async function upsert({ platform, teamId, events, channelRef, enabled, membersAck, membersAckBy, membersAckAt }) { await query( `INSERT INTO team_integration_config (platform, team_id, events, channel_ref, enabled, members_ack, members_ack_by, members_ack_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE events = VALUES(events), channel_ref = VALUES(channel_ref), enabled = VALUES(enabled), members_ack = VALUES(members_ack), members_ack_by = VALUES(members_ack_by), members_ack_at = VALUES(members_ack_at)`, [ platform, teamId === null || teamId === undefined ? null : Number(teamId), JSON.stringify(events || []), channelRef || null, enabled ? 1 : 0, membersAck ? 1 : 0, membersAckBy || null, membersAckAt || null, ], ) return getForTeam(platform, teamId) } async function remove(platform, teamId) { const res = await query('DELETE FROM team_integration_config WHERE platform = ? AND team_key = ?', [ platform, teamId === null || teamId === undefined ? 0 : Number(teamId), ]) return Number(res && res.affectedRows) || 0 } module.exports = { listForPlatform, resolveFor, getById, getForTeam, upsert, remove }