// ── Core's own scope-preference provider: Teams ──────────────────────────── // // ENGAGEMENT.md Phase 6, decision 4. `team_notification_prefs` stays exactly // where it is and keeps exactly the meaning it has had since Teams shipped; this // is the adapter that lets the generic engine read it without knowing what a Team // is. Registered here rather than at the bottom of `scopedPrefs.js` for the same // reason `coreChannels` and `transports/smtp` are: requiring a registry must not // have the side effect of populating it. // // **The two columns say different things and the mapping is not symmetric.** // // - `muted` is the Team's master switch and it silences EVERY channel. That is // what the toggle has always meant on the account screen ("mute this Team"), // and narrowing it to email would be a behaviour change nobody asked for. Note // this is belt-and-braces on the live path — `teamNotify.recipientIds` already // excludes muted users before the event is emitted — and it is here anyway so // the meaning survives an emitter that stops filtering. // - `email_mode` says nothing about any other channel, so on push or in-app this // provider returns no opinion and the stream-level preference decides. // // **Absence of a row means 'off' for email, and that is the whole reason this // provider answers for every user rather than only for the rows it finds.** The // column defaults to `'off'` and both recipient queries COALESCE to it: no row // has always meant "this person has not asked for Team email". Deferring to the // stream-level preference instead would mean a user who once switched on // `team.forum.post` email in the channels screen starts receiving mail from every // Team on the deployment — a widening, produced by a migration, of a preference // they expressed about something else. const { registerScopePreference } = require('./scopedPrefs') const teamNotify = require('../model/teams/teamNotify.model') // team_notification_prefs.email_mode → the three modes the engine speaks. The // vocabularies differ by one word and only one word: 'immediate' predates // `notification_channel_prefs`, whose ENUM says 'instant'. const EMAIL_MODE = { off: 'off', immediate: 'instant', digest: 'digest' } async function modesFor(userIds, channel, scopeId) { const teamId = Number(scopeId) if (!Number.isInteger(teamId) || teamId < 1) return new Map() const rows = await teamNotify.prefsForTeam(userIds, teamId) const byUser = new Map(rows.map((r) => [Number(r.user_id), r])) const modes = new Map() for (const userId of userIds) { const row = byUser.get(Number(userId)) if (row && Number(row.muted)) { modes.set(Number(userId), 'off') continue } if (channel !== 'email') continue // no opinion; the stream preference decides modes.set(Number(userId), EMAIL_MODE[(row && row.email_mode) || 'off'] || 'off') } return modes } registerScopePreference({ prefix: 'team', label: 'Team', modesFor, }) module.exports = { modesFor, EMAIL_MODE }