446 lines
13 KiB
JavaScript
446 lines
13 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const Joi = require('joi');
|
|
const { AlertRule, AlertLog, User, Device, Tenant, sequelize } = require('../models');
|
|
const { validateRequest } = require('../middleware/validation');
|
|
const { authenticateToken, requireRole } = require('../middleware/auth');
|
|
const MultiTenantAuth = require('../middleware/multi-tenant-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;
|
|
|
|
// Initialize multi-tenant auth to determine tenant
|
|
const multiTenantAuth = new MultiTenantAuth();
|
|
const tenantSlug = await multiTenantAuth.determineTenant(req);
|
|
|
|
if (!tenantSlug) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
}
|
|
|
|
const tenant = await Tenant.findOne({ where: { slug: tenantSlug } });
|
|
if (!tenant) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
}
|
|
|
|
// Filter alert rules by users in the same tenant
|
|
const whereClause = {};
|
|
if (is_active !== undefined) whereClause.is_active = is_active === 'true';
|
|
|
|
const alertRules = await AlertRule.findAndCountAll({
|
|
where: whereClause,
|
|
include: [{
|
|
model: User,
|
|
as: 'user',
|
|
where: { tenant_id: tenant.id },
|
|
attributes: ['id', 'username', 'email']
|
|
}],
|
|
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 {
|
|
// Initialize multi-tenant auth to determine tenant
|
|
const multiTenantAuth = new MultiTenantAuth();
|
|
const tenantSlug = await multiTenantAuth.determineTenant(req);
|
|
|
|
if (!tenantSlug) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
}
|
|
|
|
const tenant = await Tenant.findOne({ where: { slug: tenantSlug } });
|
|
if (!tenant) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
}
|
|
|
|
const alertRule = await AlertRule.findOne({
|
|
where: {
|
|
id: req.params.id
|
|
},
|
|
include: [{
|
|
model: User,
|
|
as: 'user',
|
|
where: { tenant_id: tenant.id },
|
|
attributes: ['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 {
|
|
// Initialize multi-tenant auth to determine tenant
|
|
const multiTenantAuth = new MultiTenantAuth();
|
|
const tenantSlug = await multiTenantAuth.determineTenant(req);
|
|
|
|
if (!tenantSlug) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
}
|
|
|
|
const tenant = await Tenant.findOne({ where: { slug: tenantSlug } });
|
|
if (!tenant) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
}
|
|
|
|
const alertRule = await AlertRule.findOne({
|
|
where: {
|
|
id: req.params.id
|
|
},
|
|
include: [{
|
|
model: User,
|
|
as: 'user',
|
|
where: { tenant_id: tenant.id },
|
|
attributes: ['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;
|
|
|
|
// Initialize multi-tenant auth to determine tenant
|
|
const multiTenantAuth = new MultiTenantAuth();
|
|
const tenantSlug = await multiTenantAuth.determineTenant(req);
|
|
|
|
if (!tenantSlug) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
}
|
|
|
|
const tenant = await Tenant.findOne({ where: { slug: tenantSlug } });
|
|
if (!tenant) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
}
|
|
|
|
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',
|
|
include: [{
|
|
model: User,
|
|
as: 'user',
|
|
where: { tenant_id: tenant.id },
|
|
attributes: ['id', 'username']
|
|
}],
|
|
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);
|
|
|
|
// Initialize multi-tenant auth to determine tenant
|
|
const multiTenantAuth = new MultiTenantAuth();
|
|
const tenantSlug = await multiTenantAuth.determineTenant(req);
|
|
|
|
if (!tenantSlug) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
}
|
|
|
|
const tenant = await Tenant.findOne({ where: { slug: tenantSlug } });
|
|
if (!tenant) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
}
|
|
|
|
// Get tenant's alert rules through user relationships
|
|
const userRuleIds = await AlertRule.findAll({
|
|
include: [{
|
|
model: User,
|
|
as: 'user',
|
|
where: { tenant_id: tenant.id },
|
|
attributes: []
|
|
}],
|
|
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;
|