const express = require('express'); const router = express.Router(); const Joi = require('joi'); const { AlertRule, AlertLog, User, sequelize } = require('../models'); const { validateRequest } = require('../middleware/validation'); const { authenticateToken, requireRole } = require('../middleware/auth'); const { Op } = require('sequelize'); // Validation schemas const alertRuleSchema = Joi.object({ name: Joi.string().required(), description: Joi.string().allow('').optional(), device_ids: Joi.array().items(Joi.number().integer()).optional(), drone_types: Joi.array().items(Joi.number().integer()).optional(), min_rssi: Joi.number().integer().optional(), max_rssi: Joi.number().integer().optional(), time_window: Joi.number().integer().min(60).max(3600).default(300), min_detections: Joi.number().integer().min(1).default(1), cooldown_period: Joi.number().integer().min(0).default(600), alert_channels: Joi.array().items(Joi.string().valid('sms', 'email', 'webhook')).default(['sms']), webhook_url: Joi.string().uri().allow('').optional(), active_hours: Joi.object({ start: Joi.string().pattern(/^\d{2}:\d{2}$/).optional(), end: Joi.string().pattern(/^\d{2}:\d{2}$/).optional() }).optional(), active_days: Joi.array().items(Joi.number().integer().min(1).max(7)).default([1,2,3,4,5,6,7]), priority: Joi.string().valid('low', 'medium', 'high', 'critical').default('medium') }); // GET /api/alerts/rules - Get alert rules for current user router.get('/rules', authenticateToken, async (req, res) => { try { const { limit = 50, offset = 0, is_active } = req.query; const whereClause = { user_id: req.user.id }; if (is_active !== undefined) whereClause.is_active = is_active === 'true'; const alertRules = await AlertRule.findAndCountAll({ where: whereClause, limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: alertRules.rows, pagination: { total: alertRules.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(alertRules.count / parseInt(limit)) } }); } catch (error) { console.error('Error fetching alert rules:', error); res.status(500).json({ success: false, message: 'Failed to fetch alert rules', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // POST /api/alerts/rules - Create new alert rule router.post('/rules', authenticateToken, validateRequest(alertRuleSchema), async (req, res) => { try { // Custom validation for webhook URL when webhook channel is selected if (req.body.alert_channels && req.body.alert_channels.includes('webhook')) { if (!req.body.webhook_url || req.body.webhook_url.trim() === '') { return res.status(400).json({ success: false, message: 'Validation error', errors: [{ field: 'webhook_url', message: 'Webhook URL is required when webhook channel is selected', value: req.body.webhook_url || '' }] }); } } const alertRule = await AlertRule.create({ ...req.body, user_id: req.user.id }); res.status(201).json({ success: true, data: alertRule, message: 'Alert rule created successfully' }); } catch (error) { console.error('Error creating alert rule:', error); res.status(500).json({ success: false, message: 'Failed to create alert rule', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // PUT /api/alerts/rules/:id - Update alert rule router.put('/rules/:id', authenticateToken, validateRequest(alertRuleSchema), async (req, res) => { try { const alertRule = await AlertRule.findOne({ where: { id: req.params.id, user_id: req.user.id } }); if (!alertRule) { return res.status(404).json({ success: false, message: 'Alert rule not found' }); } await alertRule.update(req.body); res.json({ success: true, data: alertRule, message: 'Alert rule updated successfully' }); } catch (error) { console.error('Error updating alert rule:', error); res.status(500).json({ success: false, message: 'Failed to update alert rule', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // DELETE /api/alerts/rules/:id - Delete alert rule router.delete('/rules/:id', authenticateToken, async (req, res) => { try { const alertRule = await AlertRule.findOne({ where: { id: req.params.id, user_id: req.user.id } }); if (!alertRule) { return res.status(404).json({ success: false, message: 'Alert rule not found' }); } await alertRule.destroy(); res.json({ success: true, message: 'Alert rule deleted successfully' }); } catch (error) { console.error('Error deleting alert rule:', error); res.status(500).json({ success: false, message: 'Failed to delete alert rule', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/alerts/logs - Get alert logs for current user router.get('/logs', authenticateToken, async (req, res) => { try { const { limit = 50, offset = 0, status, alert_type, start_date, end_date } = req.query; const whereClause = {}; if (status) whereClause.status = status; if (alert_type) whereClause.alert_type = alert_type; if (start_date || end_date) { whereClause.created_at = {}; if (start_date) whereClause.created_at[Op.gte] = new Date(start_date); if (end_date) whereClause.created_at[Op.lte] = new Date(end_date); } const alertLogs = await AlertLog.findAndCountAll({ where: whereClause, include: [{ model: AlertRule, as: 'rule', where: { user_id: req.user.id }, attributes: ['id', 'name', 'priority'] }], limit: Math.min(parseInt(limit), 200), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: alertLogs.rows, pagination: { total: alertLogs.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(alertLogs.count / parseInt(limit)) } }); } catch (error) { console.error('Error fetching alert logs:', error); res.status(500).json({ success: false, message: 'Failed to fetch alert logs', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/alerts/stats - Get alert statistics for current user router.get('/stats', authenticateToken, async (req, res) => { try { const { hours = 24 } = req.query; const timeWindow = new Date(Date.now() - hours * 60 * 60 * 1000); // Get user's alert rules const userRuleIds = await AlertRule.findAll({ where: { user_id: req.user.id }, attributes: ['id'] }).then(rules => rules.map(rule => rule.id)); if (userRuleIds.length === 0) { return res.json({ success: true, data: { total_alerts: 0, sent_alerts: 0, failed_alerts: 0, pending_alerts: 0, by_type: {}, by_status: {} } }); } const [totalAlerts, alertsByStatus, alertsByType] = await Promise.all([ AlertLog.count({ where: { alert_rule_id: { [Op.in]: userRuleIds }, created_at: { [Op.gte]: timeWindow } } }), AlertLog.findAll({ where: { alert_rule_id: { [Op.in]: userRuleIds }, created_at: { [Op.gte]: timeWindow } }, attributes: [ 'status', [sequelize.fn('COUNT', '*'), 'count'] ], group: ['status'], raw: true }), AlertLog.findAll({ where: { alert_rule_id: { [Op.in]: userRuleIds }, created_at: { [Op.gte]: timeWindow } }, attributes: [ 'alert_type', [sequelize.fn('COUNT', '*'), 'count'] ], group: ['alert_type'], raw: true }) ]); const statusCounts = alertsByStatus.reduce((acc, item) => { acc[item.status] = parseInt(item.count); return acc; }, {}); const typeCounts = alertsByType.reduce((acc, item) => { acc[item.alert_type] = parseInt(item.count); return acc; }, {}); res.json({ success: true, data: { total_alerts: totalAlerts, sent_alerts: statusCounts.sent || 0, failed_alerts: statusCounts.failed || 0, pending_alerts: statusCounts.pending || 0, by_type: typeCounts, by_status: statusCounts, time_window_hours: hours } }); } catch (error) { console.error('Error fetching alert statistics:', error); res.status(500).json({ success: false, message: 'Failed to fetch alert statistics', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); module.exports = router;