Add automated ISAC system notifications

This commit is contained in:
JB
2026-07-26 02:08:34 -04:00
parent 551a5c29a1
commit 32e10c710b
20 changed files with 392 additions and 11 deletions
+6
View File
@@ -93,6 +93,12 @@ for (const [name, definition] of [
await database.exec("UPDATE data_change_proposals SET draft_entity_id=id WHERE draft_entity_id IS NULL AND entity_id IS NULL");
await database.exec('CREATE UNIQUE INDEX IF NOT EXISTS data_change_proposals_draft_entity_idx ON data_change_proposals(draft_entity_id) WHERE draft_entity_id IS NOT NULL');
const notificationColumns = await (await database.prepare("PRAGMA table_info('notifications')")).all();
if (!notificationColumns.some((column) => column.name === 'actions_json')) {
console.log('Adding structured action callouts to notifications.');
await database.exec('ALTER TABLE notifications ADD COLUMN actions_json TEXT');
}
const proposalDefinition = await (await database.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='data_change_proposals'"
)).get();
+66
View File
@@ -0,0 +1,66 @@
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 delivery.id AS delivery_id,delivery.user_id,message.*
FROM automated_system_message_deliveries delivery
JOIN automated_system_messages message ON message.id=delivery.message_id
WHERE delivery.delivered_at IS NULL AND delivery.scheduled_for<=? AND message.enabled=1
ORDER BY delivery.scheduled_for LIMIT 250`)).all(now);
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(/\/+$/, '');
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (character) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[character]);
let delivered = 0;
for (const message of due) {
const notificationId = randomUUID();
await (await database.prepare(`INSERT INTO notifications
(id,user_id,category,source,title,body,link,actions_json,mandatory,created_at)
VALUES(?,?,'isac',?,?,?,?,?,1,?)`)).run(
notificationId, message.user_id, message.source, message.title, message.body,
message.link ?? null, message.actions_json ?? null, now
);
await (await database.prepare(`UPDATE automated_system_message_deliveries
SET delivered_at=?,notification_id=? WHERE id=? AND delivered_at IS NULL`))
.run(now, notificationId, message.delivery_id);
const preference = await (await database.prepare(
"SELECT email_frequency FROM notification_preferences WHERE user_id=? AND category='isac'"
)).get(message.user_id);
if (transport && preference?.email_frequency === 'immediate') {
const identity = await (await database.prepare('SELECT email FROM user_emails WHERE user_id=?')).get(message.user_id);
if (typeof identity?.email === 'string') {
try {
await transport.sendMail({
from: process.env.SMTP_FROM,
to: identity.email,
subject: `SHD.build · ${message.title}`,
text: `${message.body}\n\n${message.link ? `${origin}${message.link}` : origin}`,
html: `<div style="background:#0a0c0d;color:#ecebe5;padding:28px;font-family:Arial,sans-serif"><p style="color:#f6a821;font-size:12px;letter-spacing:2px">${escapeHtml(message.source)}</p><h1>${escapeHtml(message.title)}</h1><p style="color:#a5aaab">${escapeHtml(message.body)}</p>${message.link ? `<a style="color:#f6a821" href="${escapeHtml(`${origin}${message.link}`)}">OPEN SHD.BUILD</a>` : ''}</div>`
});
await (await database.prepare('UPDATE notifications SET emailed_at=? WHERE id=?')).run(now, notificationId);
} catch (error) {
console.error(`Unable to email automated notification ${notificationId}`, error);
}
}
}
delivered += 1;
}
console.log(`Delivered ${delivered} automated ISAC system notification${delivered === 1 ? '' : 's'}.`);