const { query } = require('../../utils/db') const listByUser = (userId) => query( 'SELECT stream_id, channel, mode FROM notification_channel_prefs WHERE user_id = ? ORDER BY stream_id, channel', [userId], ) // One (user, stream, channel) row. Upsert rather than insert-or-update in app // code: the primary key is exactly the triple, so MariaDB decides, and two // concurrent PUTs from a phone and a browser cannot race into a duplicate-key // error. const upsert = (userId, streamId, channel, mode) => query( `INSERT INTO notification_channel_prefs (user_id, stream_id, channel, mode) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE mode = VALUES(mode)`, [userId, streamId, channel, mode], ) // Every push row this user holds that is NOT in `keep`, set to 'off'. The legacy // whole-set PUT's other half: it says "these streams and no others", and the // rows it is silent about have to stop meaning 'instant'. // // It sets rather than deletes, so a user's explicit "no" survives a later change // to push's `defaultMode` (§3.1 / channels.js). Deleting would fold "I turned // this off" back into "I never said", and those are the same thing only for as // long as the default happens to be 'off'. async function offPushExcept(userId, keep) { if (!keep.length) { return query( "UPDATE notification_channel_prefs SET mode = 'off' WHERE user_id = ? AND channel = 'push' AND mode <> 'off'", [userId], ) } const marks = keep.map(() => '?').join(', ') return query( `UPDATE notification_channel_prefs SET mode = 'off' WHERE user_id = ? AND channel = 'push' AND mode <> 'off' AND stream_id NOT IN (${marks})`, [userId, ...keep], ) } /** * Turn one channel off for every named stream — the deployment-wide unsubscribe * (ENGAGEMENT.md Phase 6). * * It WRITES a row per stream rather than updating the rows that happen to exist, * and the difference is the same one `offPushExcept` argues: absence means the * channel's `defaultMode`, so updating only what is there would leave a user * unsubscribed today and re-subscribed the day a channel ships a non-off default. * An unsubscribe has to be a statement, not the absence of one. */ async function offForChannel(userId, channel, streamIds) { const ids = [...new Set(streamIds)].filter((s) => typeof s === 'string' && s) if (!ids.length) return null const values = ids.map(() => '(?, ?, ?, ?)').join(', ') const params = ids.flatMap((streamId) => [userId, streamId, channel, 'off']) return query( `INSERT INTO notification_channel_prefs (user_id, stream_id, channel, mode) VALUES ${values} ON DUPLICATE KEY UPDATE mode = VALUES(mode)`, params, ) } module.exports = { listByUser, upsert, offPushExcept, offForChannel }