import { getDb } from '../config/database.js'; import { log } from '../config/logger.js'; import { createNotification } from '../config/notifications.js'; const CHECK_INTERVAL_MS = 60_000; // every minute let timer = null; /** * Check for upcoming calendar events that need a reminder notification fired. * Runs every minute. Updates `reminder_sent_at` after firing so reminders * are never sent twice. Also resets `reminder_sent_at` to NULL when * start_time or reminder_minutes is changed (handled in calendar route). */ async function runCheck() { try { const db = getDb(); // Fetch all events that have a reminder configured and haven't been sent yet const pending = await db.all(` SELECT ce.id, ce.uid, ce.title, ce.start_time, ce.reminder_minutes, ce.user_id, ce.room_uid, ce.color FROM calendar_events ce WHERE ce.reminder_minutes IS NOT NULL AND ce.reminder_sent_at IS NULL `); if (pending.length === 0) return; const now = new Date(); const toFire = pending.filter(ev => { const start = new Date(ev.start_time); // Don't fire reminders for events that started more than 10 minutes ago (server downtime tolerance) if (start < new Date(now.getTime() - 10 * 60_000)) return false; const reminderTime = new Date(start.getTime() - ev.reminder_minutes * 60_000); return reminderTime <= now; }); for (const ev of toFire) { try { // Mark as sent immediately to prevent double-fire even if notification creation fails await db.run( 'UPDATE calendar_events SET reminder_sent_at = ? WHERE id = ?', [now.toISOString(), ev.id], ); const start = new Date(ev.start_time); const timeStr = start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const dateStr = start.toLocaleDateString([], { day: 'numeric', month: 'short' }); const body = `${dateStr} ยท ${timeStr}`; const link = '/calendar'; // Notify the owner await createNotification(ev.user_id, 'calendar_reminder', ev.title, body, link); // Notify all accepted share users as well const shares = await db.all( 'SELECT user_id FROM calendar_event_shares WHERE event_id = ?', [ev.id], ); for (const { user_id } of shares) { await createNotification(user_id, 'calendar_reminder', ev.title, body, link); } log.server.info(`Calendar reminder fired for event ${ev.uid} (id=${ev.id})`); } catch (evErr) { log.server.error(`Calendar reminder failed for event ${ev.id}: ${evErr.message}`); } } } catch (err) { log.server.error(`Calendar reminder job error: ${err.message}`); } } export function startCalendarReminders() { if (timer) return; // Slight delay on startup so DB is fully ready setTimeout(() => { runCheck(); timer = setInterval(runCheck, CHECK_INTERVAL_MS); }, 5_000); log.server.info('Calendar reminder job started'); } export function stopCalendarReminders() { if (timer) { clearInterval(timer); timer = null; } }