const express = require('express'); const { validateURL, validateToken } = require('../input-validator'); const { paginate, parsePaginationParams } = require('../pagination'); module.exports = function(ctx) { const router = express.Router(); // GET /config — Get notification configuration (sensitive data redacted) router.get('/config', ctx.asyncHandler(async (req, res) => { const notificationConfig = ctx.notification.getConfig(); // Return config without sensitive data const safeConfig = { enabled: notificationConfig.enabled, providers: { discord: { enabled: notificationConfig.providers.discord?.enabled || false, configured: !!notificationConfig.providers.discord?.webhookUrl, }, telegram: { enabled: notificationConfig.providers.telegram?.enabled || false, configured: !!(notificationConfig.providers.telegram?.botToken && notificationConfig.providers.telegram?.chatId), }, ntfy: { enabled: notificationConfig.providers.ntfy?.enabled || false, configured: !!notificationConfig.providers.ntfy?.topic, serverUrl: notificationConfig.providers.ntfy?.serverUrl || 'https://ntfy.sh', }, }, events: notificationConfig.events, healthCheck: notificationConfig.healthCheck, }; res.json({ success: true, config: safeConfig }); }, 'notifications-config-get')); // POST /config — Update notification configuration router.post('/config', ctx.asyncHandler(async (req, res) => { const { enabled, providers, events, healthCheck } = req.body; const notificationConfig = ctx.notification.getConfig(); // Validate provider webhook URLs and tokens if (providers) { if (providers.discord?.webhookUrl) { try { validateURL(providers.discord.webhookUrl); } catch (validationErr) { return ctx.errorResponse(res, 400, 'Invalid Discord webhook URL'); } } if (providers.telegram?.botToken) { try { validateToken(providers.telegram.botToken); } catch (validationErr) { return ctx.errorResponse(res, 400, 'Invalid Telegram bot token format'); } } if (providers.ntfy?.serverUrl) { try { validateURL(providers.ntfy.serverUrl); } catch (validationErr) { return ctx.errorResponse(res, 400, 'Invalid ntfy server URL'); } } if (providers.ntfy?.topic) { const topicRegex = /^[a-zA-Z0-9_-]{1,64}$/; if (!topicRegex.test(providers.ntfy.topic)) { return ctx.errorResponse(res, 400, 'Invalid ntfy topic (alphanumeric, hyphens, underscores only, max 64 chars)'); } } } // Update enabled state if (typeof enabled === 'boolean') { notificationConfig.enabled = enabled; } // Update providers (only update provided fields) if (providers) { if (providers.discord) { notificationConfig.providers.discord = { ...notificationConfig.providers.discord, ...providers.discord, }; } if (providers.telegram) { notificationConfig.providers.telegram = { ...notificationConfig.providers.telegram, ...providers.telegram, }; } if (providers.ntfy) { notificationConfig.providers.ntfy = { ...notificationConfig.providers.ntfy, ...providers.ntfy, }; } } // Update events if (events) { notificationConfig.events = { ...notificationConfig.events, ...events }; } // Update health check settings if (healthCheck) { const wasEnabled = notificationConfig.healthCheck?.enabled; notificationConfig.healthCheck = { ...notificationConfig.healthCheck, ...healthCheck }; // Restart daemon if settings changed if (healthCheck.enabled !== wasEnabled || healthCheck.intervalMinutes) { if (notificationConfig.healthCheck.enabled) { ctx.notification.startHealthDaemon(); } else { ctx.notification.stopHealthDaemon(); } } } await ctx.notification.saveConfig(); res.json({ success: true, message: 'Notification config updated' }); }, 'notifications-config-update')); // POST /test — Test notification delivery router.post('/test', ctx.asyncHandler(async (req, res) => { const { provider } = req.body; if (provider) { // Test specific provider let result; switch (provider) { case 'discord': result = await ctx.notification.sendDiscord('Test Notification', 'This is a test notification from DashCaddy.', 'info'); break; case 'telegram': result = await ctx.notification.sendTelegram('Test Notification', 'This is a test notification from DashCaddy.', 'info'); break; case 'ntfy': result = await ctx.notification.sendNtfy('Test Notification', 'This is a test notification from DashCaddy.', 'info'); break; default: return ctx.errorResponse(res, 400, 'Unknown provider'); } res.json({ success: result.success, provider, error: result.error }); } else { // Test all enabled providers const result = await ctx.notification.send('test', 'Test Notification', 'This is a test notification from DashCaddy.', 'info'); res.json({ success: true, ...result }); } }, 'notifications-test')); // GET /history — Get notification history router.get('/history', ctx.asyncHandler(async (req, res) => { const notificationHistory = ctx.notification.getHistory(); const paginationParams = parsePaginationParams(req.query); if (paginationParams) { const result = paginate(notificationHistory, paginationParams); res.json({ success: true, history: result.data, total: notificationHistory.length, pagination: result.pagination }); } else { const limit = parseInt(req.query.limit) || 50; res.json({ success: true, history: notificationHistory.slice(0, limit), total: notificationHistory.length, }); } }, 'notifications-history')); // DELETE /history — Clear notification history router.delete('/history', ctx.asyncHandler(async (req, res) => { ctx.notification.clearHistory(); res.json({ success: true, message: 'Notification history cleared' }); }, 'notifications-history-clear')); // POST /health-check — Manually trigger health check router.post('/health-check', ctx.asyncHandler(async (req, res) => { await ctx.notification.checkHealth(); const notificationConfig = ctx.notification.getConfig(); res.json({ success: true, lastCheck: notificationConfig.healthCheck.lastCheck, containersMonitored: Object.keys(ctx.notification.getHealthState()).length, }); }, 'notifications-health-check')); return router; };