195 lines
6.4 KiB
TypeScript
195 lines
6.4 KiB
TypeScript
// @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';
|
||
const express = require('express');
|
||
const { log } = require('../utils/logger.cts');
|
||
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');
|
||
|
||
// ── Admin: SMTP configuration ─────────────────────────────────────────────────
|
||
|
||
// GET /api/notifications/admin — read all SMTP settings (password masked)
|
||
router.get('/admin', requireAuth, requireAdmin, (req: Req, res: Res) => {
|
||
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',
|
||
];
|
||
const settings = {};
|
||
for (const k of keys) settings[k] = getSetting(k) || '';
|
||
if (!settings.reminder_hour) settings.reminder_hour = '6';
|
||
// 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) => {
|
||
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',
|
||
];
|
||
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('•')) {
|
||
setSetting('notify_smtp_password', encryptSecret(req.body.notify_smtp_password));
|
||
}
|
||
// Reminder send-hour (0–23, 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) {
|
||
log.error('[notifications] Failed to reschedule daily worker:', err.message);
|
||
}
|
||
}
|
||
res.json({ success: true });
|
||
});
|
||
|
||
// POST /api/notifications/test — send a test email
|
||
router.post('/test', requireAuth, requireAdmin, async (req: Req, res: Res) => {
|
||
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(
|
||
`
|
||
SELECT notification_email, notifications_enabled,
|
||
notify_3d, notify_1d, notify_due, notify_overdue, notify_amount_change
|
||
FROM users WHERE id = ?
|
||
`,
|
||
)
|
||
.get(req.user.id);
|
||
|
||
res.json({
|
||
smtp_enabled: getSetting('notify_smtp_enabled') === 'true',
|
||
allow_user_config: getSetting('notify_allow_user_config') === 'true',
|
||
notification_email: user.notification_email || '',
|
||
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,
|
||
});
|
||
});
|
||
|
||
// PUT /api/notifications/me — save user prefs
|
||
router.put('/me', requireAuth, requireUser, (req: Req, res: Res) => {
|
||
const db = getDb();
|
||
const {
|
||
notification_email,
|
||
notifications_enabled,
|
||
notify_3d,
|
||
notify_1d,
|
||
notify_due,
|
||
notify_overdue,
|
||
notify_amount_change,
|
||
} = req.body;
|
||
|
||
db.prepare(
|
||
`
|
||
UPDATE users SET
|
||
notification_email = ?,
|
||
notifications_enabled = ?,
|
||
notify_3d = ?,
|
||
notify_1d = ?,
|
||
notify_due = ?,
|
||
notify_overdue = ?,
|
||
notify_amount_change = ?,
|
||
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,
|
||
notify_overdue !== false ? 1 : 0,
|
||
notify_amount_change !== false ? 1 : 0,
|
||
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 });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|