// ── Team activity retention worker ────────────────────────────────────────── // // TEAMS.md §4.2's nightly prune: an age horizon (`team_activity_retain_days`, // default 90) and a per-Team row cap (`team_activity_row_cap`, default 2000). // Both live in `settings`, so an operator can tighten a busy shard without a // deploy. // // Same in-process shape as utils/announceWorker and middleware/botScore's // sweeper — setInterval + unref + stop(), wired into server.js start/shutdown. // There is no cron in this stack and adding one for a single daily DELETE would // be a dependency to justify at every future upgrade. // // **The first run is delayed rather than immediate.** A prune at boot would put a // table-wide DELETE in front of the first request on every restart, and a // deployment that is crash-looping would run it on every loop. Five minutes in is // past the point where a boot has either succeeded or failed. const teamActivity = require('../model/teams/teamActivity.model') const log = require('./logger')('teams') // Nightly, per §4.2. Not aligned to a wall-clock hour: the work is proportional // to what arrived rather than to when it is done, and pinning it to 03:00 would // mean a process that restarts each afternoon never prunes at all. const INTERVAL_MS = Number(process.env.TEAM_ACTIVITY_PRUNE_MS) || 24 * 60 * 60 * 1000 const FIRST_RUN_MS = Number(process.env.TEAM_ACTIVITY_PRUNE_DELAY_MS) || 5 * 60 * 1000 let timer = null let firstRun = null /** One prune. Never throws — it runs on a timer with nobody to catch it. */ async function tick() { try { return await teamActivity.prune() } catch (err) { log.error('team activity prune failed', { message: err.message }) return null } } function start() { if (timer || firstRun) return timer firstRun = setTimeout(() => { firstRun = null tick() timer = setInterval(() => { tick() }, INTERVAL_MS) if (timer.unref) timer.unref() }, FIRST_RUN_MS) if (firstRun.unref) firstRun.unref() log.info('team activity retention started', { intervalMs: INTERVAL_MS, firstRunMs: FIRST_RUN_MS }) return timer } function stop() { if (firstRun) { clearTimeout(firstRun) firstRun = null } if (timer) { clearInterval(timer) timer = null } } module.exports = { start, stop, tick, INTERVAL_MS, FIRST_RUN_MS }