BillTracker/routes/notifications.cts

194 lines
6.4 KiB
TypeScript
Raw Normal View History

// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
import type { Req, Res, Next } from '../types/http';
2026-05-03 19:51:57 -05:00
const express = require('express');
const router = express.Router();
const { getDb, getSetting, setSetting } = require('../db/database.cts');
const { requireAuth, requireUser, requireAdmin } = require('../middleware/requireAuth.cts');
const { sendTestEmail } = require('../services/notificationService.cts');
const { sendTestPush } = require('../services/notificationService.cts')._push || {};
const { decryptSecret } = require('../services/encryptionService.cts');
const { encryptSecret } = require('../services/encryptionService.cts');
2026-05-03 19:51:57 -05:00
// ── Admin: SMTP configuration ─────────────────────────────────────────────────
// GET /api/notifications/admin — read all SMTP settings (password masked)
router.get('/admin', requireAuth, requireAdmin, (req: Req, res: Res) => {
2026-05-03 19:51:57 -05:00
const keys = [
'notify_smtp_enabled',
'notify_sender_name',
'notify_sender_address',
'notify_smtp_host',
'notify_smtp_port',
'notify_smtp_encryption',
'notify_smtp_self_signed',
'notify_smtp_username',
'notify_smtp_password',
'notify_allow_user_config',
'notify_global_recipient',
'reminder_hour',
2026-05-03 19:51:57 -05:00
];
const settings = {};
for (const k of keys) settings[k] = getSetting(k) || '';
if (!settings.reminder_hour) settings.reminder_hour = '6';
2026-05-03 19:51:57 -05:00
// Mask password in response
if (settings.notify_smtp_password) settings.notify_smtp_password = '••••••••';
res.json(settings);
});
// PUT /api/notifications/admin — save SMTP settings
router.put('/admin', requireAuth, requireAdmin, (req: Req, res: Res) => {
2026-05-03 19:51:57 -05:00
const allowed = [
'notify_smtp_enabled',
'notify_sender_name',
'notify_sender_address',
'notify_smtp_host',
'notify_smtp_port',
'notify_smtp_encryption',
'notify_smtp_self_signed',
'notify_smtp_username',
'notify_allow_user_config',
'notify_global_recipient',
2026-05-03 19:51:57 -05:00
];
for (const key of allowed) {
if (req.body[key] !== undefined) setSetting(key, req.body[key]);
}
// Only update password if a real value was sent (not the masked placeholder)
if (req.body.notify_smtp_password && !req.body.notify_smtp_password.startsWith('•')) {
2026-05-31 15:06:10 -05:00
setSetting('notify_smtp_password', encryptSecret(req.body.notify_smtp_password));
2026-05-03 19:51:57 -05:00
}
// Reminder send-hour (023, global): persist clamped + live-reschedule the cron.
if (req.body.reminder_hour !== undefined) {
const h = parseInt(req.body.reminder_hour, 10);
const hour = Number.isInteger(h) && h >= 0 && h <= 23 ? h : 6;
setSetting('reminder_hour', String(hour));
try {
require('../workers/dailyWorker.cts').rescheduleDailyWorker();
} catch (err) {
console.error('[notifications] Failed to reschedule daily worker:', err.message);
}
}
2026-05-03 19:51:57 -05:00
res.json({ success: true });
});
// POST /api/notifications/test — send a test email
router.post('/test', requireAuth, requireAdmin, async (req: Req, res: Res) => {
2026-05-03 19:51:57 -05:00
const { to } = req.body;
if (!to) return res.status(400).json({ error: 'Recipient address required' });
try {
await sendTestEmail(to);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ── User: notification preferences ───────────────────────────────────────────
// GET /api/notifications/me — user prefs + whether admin has enabled it
router.get('/me', requireAuth, requireUser, (req: Req, res: Res) => {
const db = getDb();
const user = db
.prepare(
`
2026-05-03 19:51:57 -05:00
SELECT notification_email, notifications_enabled,
notify_3d, notify_1d, notify_due, notify_overdue, notify_amount_change
2026-05-03 19:51:57 -05:00
FROM users WHERE id = ?
`,
)
.get(req.user.id);
2026-05-03 19:51:57 -05:00
res.json({
smtp_enabled: getSetting('notify_smtp_enabled') === 'true',
allow_user_config: getSetting('notify_allow_user_config') === 'true',
notification_email: user.notification_email || '',
2026-05-03 19:51:57 -05:00
notifications_enabled: !!user.notifications_enabled,
notify_3d: !!user.notify_3d,
notify_1d: !!user.notify_1d,
notify_due: !!user.notify_due,
notify_overdue: !!user.notify_overdue,
notify_amount_change: user.notify_amount_change !== 0,
2026-05-03 19:51:57 -05:00
});
});
// PUT /api/notifications/me — save user prefs
router.put('/me', requireAuth, requireUser, (req: Req, res: Res) => {
2026-05-03 19:51:57 -05:00
const db = getDb();
const {
notification_email,
notifications_enabled,
notify_3d,
notify_1d,
notify_due,
notify_overdue,
notify_amount_change,
2026-05-03 19:51:57 -05:00
} = req.body;
db.prepare(
`
2026-05-03 19:51:57 -05:00
UPDATE users SET
notification_email = ?,
notifications_enabled = ?,
notify_3d = ?,
notify_1d = ?,
notify_due = ?,
notify_overdue = ?,
notify_amount_change = ?,
2026-05-03 19:51:57 -05:00
updated_at = datetime('now')
WHERE id = ?
`,
).run(
notification_email || null,
notifications_enabled ? 1 : 0,
notify_3d !== false ? 1 : 0,
notify_1d !== false ? 1 : 0,
notify_due !== false ? 1 : 0,
2026-05-03 19:51:57 -05:00
notify_overdue !== false ? 1 : 0,
notify_amount_change !== false ? 1 : 0,
2026-05-03 19:51:57 -05:00
req.user.id,
);
res.json({ success: true });
});
// POST /api/notifications/test-push — send a test push notification to the current user
router.post('/test-push', requireAuth, requireUser, async (req: Req, res: Res) => {
const db = getDb();
const user = db
.prepare(
`
SELECT notify_push_enabled, push_channel, push_url, push_token, push_chat_id
FROM users WHERE id = ?
`,
)
.get(req.user.id);
if (!user?.push_channel || !user?.push_url) {
return res.status(400).json({ error: 'Push notification channel not configured' });
}
// Decrypt for sending
const safeDecrypt = (v) => {
try {
return v ? decryptSecret(v) : '';
} catch {
return v || '';
}
};
const userForPush = {
...user,
notify_push_enabled: 1,
push_url: safeDecrypt(user.push_url),
push_token: safeDecrypt(user.push_token),
};
try {
if (!sendTestPush) throw new Error('Push service not initialised');
await sendTestPush(userForPush);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
2026-05-03 19:51:57 -05:00
module.exports = router;