import { connect } from '@tursodatabase/database'; import nodemailer from 'nodemailer'; import { randomUUID } from 'node:crypto'; import { resolve } from 'node:path'; const database = await connect(resolve(process.env.TDR_DATABASE_PATH || './data/shd-build.db')); const now = new Date().toISOString(); const due = await (await database.prepare(`SELECT * FROM oversight_notification_queue WHERE delivered_at IS NULL AND deliver_after<=? ORDER BY user_id,oversight_category,created_at`)).all(now); const groups = new Map(); for (const item of due) { const key = `${item.user_id}:${item.oversight_category}:${item.frequency}:${item.deliver_after}`; if (!groups.has(key)) groups.set(key, []); groups.get(key).push(item); } const smtpConfigured = Boolean(process.env.SMTP_HOST && process.env.SMTP_FROM); const transport = smtpConfigured ? nodemailer.createTransport({ host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT || 587), secure: process.env.SMTP_SECURE === 'true', auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD } : undefined, requireTLS: process.env.SMTP_SECURE !== 'true', disableFileAccess: true, disableUrlAccess: true }) : null; const origin = (process.env.APP_ORIGIN || 'http://localhost:5173').replace(/\/+$/, ''); let delivered = 0; for (const items of groups.values()) { const first = items[0]; const categoryLabel = first.oversight_category === 'avatars' ? 'avatar' : 'data change'; const title = `${items.length} ${categoryLabel} review${items.length === 1 ? '' : 's'} awaiting Oversight`; const details = items.slice(0, 10).map((item) => `• ${item.summary}`).join('\n'); const remainder = items.length > 10 ? `\n• ${items.length - 10} additional review items` : ''; const body = `${details}${remainder}`; const notificationId = randomUUID(); await (await database.prepare(`INSERT INTO notifications (id,user_id,category,source,title,body,link,mandatory,created_at) VALUES(?,?,'isac','ISAC OVERSIGHT NETWORK',?,?,?,1,?)`)).run( notificationId, first.user_id, title, body, first.link, now ); for (const item of items) { await (await database.prepare('UPDATE oversight_notification_queue SET delivered_at=? WHERE id=? AND delivered_at IS NULL')).run(now, item.id); } const preference = await (await database.prepare( "SELECT email_frequency FROM notification_preferences WHERE user_id=? AND category='isac'" )).get(first.user_id); if (transport && preference?.email_frequency === 'immediate') { const identity = await (await database.prepare('SELECT email FROM user_emails WHERE user_id=?')).get(first.user_id); if (typeof identity?.email === 'string') { try { await transport.sendMail({ from: process.env.SMTP_FROM, to: identity.email, subject: `SHD.build · ${title}`, text: `${body}\n\n${origin}${first.link}`, html: `
` }); await (await database.prepare('UPDATE notifications SET emailed_at=? WHERE id=?')).run(now, notificationId); } catch (error) { console.error(`Unable to email Oversight digest ${notificationId}`, error); } } } delivered += items.length; } console.log(`Delivered ${delivered} queued Oversight review alert${delivered === 1 ? '' : 's'} in ${groups.size} digest${groups.size === 1 ? '' : 's'}.`);