Files
MixedAssets/server/services/notifications.js
2026-06-05 04:15:24 -05:00

119 lines
4.5 KiB
JavaScript

const nodemailer = require('nodemailer');
const { all, audit, now, run } = require('../db');
function readSettings() {
const rows = all('SELECT key, value FROM app_settings');
return Object.fromEntries(rows.map((row) => [row.key, row.value]));
}
// Raw config (includes password) — for sending mail only.
function getConfig() {
const s = readSettings();
return {
host: s.smtp_host || '',
port: Number(s.smtp_port || 587),
secure: s.smtp_secure === 'true',
user: s.smtp_user || '',
password: s.smtp_password || '',
from: s.smtp_from || s.smtp_user || '',
enabled: s.alerts_enabled === 'true',
leadDays: Number(s.alerts_lead_days || 30),
recipients: String(s.alerts_recipients || '').split(',').map((x) => x.trim()).filter(Boolean),
webhookEnabled: s.webhook_enabled === 'true',
webhookUrl: s.webhook_url || '',
webhookSecret: s.webhook_secret || ''
};
}
// Safe config for the API/UI — password is never returned, only whether it is set.
function publicConfig() {
const c = getConfig();
return {
smtp_host: c.host,
smtp_port: c.port,
smtp_secure: c.secure,
smtp_user: c.user,
smtp_from: c.from,
smtp_password_set: Boolean(c.password),
alerts_enabled: c.enabled,
alerts_lead_days: c.leadDays,
alerts_recipients: c.recipients.join(', '),
configured: Boolean(c.host),
webhook_enabled: c.webhookEnabled,
webhook_url: c.webhookUrl,
webhook_secret_set: Boolean(c.webhookSecret),
webhook_configured: Boolean(c.webhookUrl)
};
}
function setValue(key, value) {
run(
'INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at',
[key, String(value), now()]
);
}
function saveConfig(body, userId) {
if (body.smtp_host !== undefined) setValue('smtp_host', body.smtp_host || '');
if (body.smtp_port !== undefined) setValue('smtp_port', Number(body.smtp_port) || 587);
if (body.smtp_secure !== undefined) setValue('smtp_secure', body.smtp_secure ? 'true' : 'false');
if (body.smtp_user !== undefined) setValue('smtp_user', body.smtp_user || '');
if (body.smtp_from !== undefined) setValue('smtp_from', body.smtp_from || '');
if (body.smtp_password) setValue('smtp_password', body.smtp_password); // only overwrite when a new value is supplied
if (body.alerts_enabled !== undefined) setValue('alerts_enabled', body.alerts_enabled ? 'true' : 'false');
if (body.alerts_lead_days !== undefined) setValue('alerts_lead_days', Number(body.alerts_lead_days) || 30);
if (body.alerts_recipients !== undefined) setValue('alerts_recipients', body.alerts_recipients || '');
if (body.webhook_enabled !== undefined) setValue('webhook_enabled', body.webhook_enabled ? 'true' : 'false');
if (body.webhook_url !== undefined) setValue('webhook_url', body.webhook_url || '');
if (body.webhook_secret_clear) setValue('webhook_secret', '');
else if (body.webhook_secret) setValue('webhook_secret', body.webhook_secret); // only overwrite when a new value is supplied
audit(userId, 'update', 'notification_settings', 'smtp', null, {
...body,
smtp_password: body.smtp_password ? '[set]' : undefined,
webhook_secret: body.webhook_secret ? '[set]' : undefined
});
return publicConfig();
}
function getTransport() {
const c = getConfig();
if (!c.host) {
throw Object.assign(new Error('SMTP host is not configured'), { status: 400 });
}
return nodemailer.createTransport({
host: c.host,
port: c.port,
secure: c.secure,
auth: c.user ? { user: c.user, pass: c.password } : undefined
});
}
async function sendMail({ to, subject, html, text }) {
const c = getConfig();
const recipients = to ? (Array.isArray(to) ? to : [to]) : c.recipients;
if (!recipients.length) {
throw Object.assign(new Error('No recipients are configured'), { status: 400 });
}
const transport = getTransport();
return transport.sendMail({
from: c.from || c.user,
to: recipients.join(', '),
subject,
html,
text
});
}
async function sendTestEmail(to, userId) {
const info = await sendMail({
to: to ? [to] : null,
subject: 'MixedAssets test email',
html: '<p>Your MixedAssets SMTP settings are working. This is a test message.</p>',
text: 'Your MixedAssets SMTP settings are working. This is a test message.'
});
audit(userId, 'send', 'test_email', null, null, { to });
return { messageId: info.messageId, accepted: info.accepted };
}
module.exports = { getConfig, publicConfig, saveConfig, sendMail, sendTestEmail };