feat(calendar): add reminder functionality for events with notifications
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
This commit is contained in:
90
server/jobs/calendarReminders.js
Normal file
90
server/jobs/calendarReminders.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user