Fix jwt-token
This commit is contained in:
87
server/migrations/20250922000002-add-alert-event-id.js
Normal file
87
server/migrations/20250922000002-add-alert-event-id.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Migration: Add alert_event_id to alert_logs table
|
||||||
|
* This migration adds alert_event_id field to group related alerts (SMS, email, webhook)
|
||||||
|
* that are part of the same detection event
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
try {
|
||||||
|
// Check if alert_logs table exists first
|
||||||
|
const tables = await queryInterface.showAllTables();
|
||||||
|
if (!tables.includes('alert_logs')) {
|
||||||
|
console.log('⚠️ Alert_logs table does not exist yet, skipping alert event ID migration...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if alert_event_id column already exists
|
||||||
|
const tableDescription = await queryInterface.describeTable('alert_logs');
|
||||||
|
|
||||||
|
if (!tableDescription.alert_event_id) {
|
||||||
|
// Add alert_event_id column
|
||||||
|
await queryInterface.addColumn('alert_logs', 'alert_event_id', {
|
||||||
|
type: Sequelize.UUID,
|
||||||
|
allowNull: true,
|
||||||
|
comment: 'Groups related alerts (SMS, email, webhook) that are part of the same detection event'
|
||||||
|
});
|
||||||
|
console.log('✅ Added alert_event_id column to alert_logs table');
|
||||||
|
|
||||||
|
// Add index for alert_event_id for better query performance
|
||||||
|
try {
|
||||||
|
await queryInterface.addIndex('alert_logs', ['alert_event_id'], {
|
||||||
|
name: 'alert_logs_alert_event_id_idx'
|
||||||
|
});
|
||||||
|
console.log('✅ Added index on alert_logs.alert_event_id');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.parent?.code === '42P07') { // Index already exists
|
||||||
|
console.log('⚠️ Index alert_logs_alert_event_id already exists, skipping...');
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Column alert_event_id already exists in alert_logs table, skipping...');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in migration 20250922000002-add-alert-event-id:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
try {
|
||||||
|
// Check if alert_logs table exists
|
||||||
|
const tables = await queryInterface.showAllTables();
|
||||||
|
if (!tables.includes('alert_logs')) {
|
||||||
|
console.log('⚠️ Alert_logs table does not exist, skipping migration rollback...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if alert_event_id column exists
|
||||||
|
const tableDescription = await queryInterface.describeTable('alert_logs');
|
||||||
|
|
||||||
|
if (tableDescription.alert_event_id) {
|
||||||
|
// Remove index first
|
||||||
|
try {
|
||||||
|
await queryInterface.removeIndex('alert_logs', 'alert_logs_alert_event_id_idx');
|
||||||
|
console.log('✅ Removed index alert_logs_alert_event_id_idx');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('⚠️ Index alert_logs_alert_event_id_idx might not exist, continuing...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove column
|
||||||
|
await queryInterface.removeColumn('alert_logs', 'alert_event_id');
|
||||||
|
console.log('✅ Removed alert_event_id column from alert_logs table');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Column alert_event_id does not exist in alert_logs table, skipping...');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in migration rollback 20250922000002-add-alert-event-id:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -31,6 +31,11 @@ module.exports = (sequelize) => {
|
|||||||
key: 'id'
|
key: 'id'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
alert_event_id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: true,
|
||||||
|
comment: 'Groups related alerts (SMS, email, webhook) that are part of the same detection event'
|
||||||
|
},
|
||||||
alert_type: {
|
alert_type: {
|
||||||
type: DataTypes.ENUM('sms', 'email', 'webhook', 'push'),
|
type: DataTypes.ENUM('sms', 'email', 'webhook', 'push'),
|
||||||
allowNull: true, // Allow null for testing
|
allowNull: true, // Allow null for testing
|
||||||
@@ -111,6 +116,9 @@ module.exports = (sequelize) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: ['alert_type', 'status']
|
fields: ['alert_type', 'status']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: ['alert_event_id']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const twilio = require('twilio');
|
|||||||
const { AlertRule, AlertLog, User, Device, DroneDetection } = require('../models');
|
const { AlertRule, AlertLog, User, Device, DroneDetection } = require('../models');
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const { getDroneTypeInfo } = require('../utils/droneTypes');
|
const { getDroneTypeInfo } = require('../utils/droneTypes');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
class AlertService {
|
class AlertService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -442,6 +443,9 @@ class AlertService {
|
|||||||
|
|
||||||
async triggerAlert(rule, detection, threatAssessment) {
|
async triggerAlert(rule, detection, threatAssessment) {
|
||||||
try {
|
try {
|
||||||
|
// Generate unique event ID to group related alerts
|
||||||
|
const alertEventId = uuidv4();
|
||||||
|
|
||||||
const user = rule.user;
|
const user = rule.user;
|
||||||
const device = await Device.findByPk(detection.device_id);
|
const device = await Device.findByPk(detection.device_id);
|
||||||
|
|
||||||
@@ -461,19 +465,19 @@ class AlertService {
|
|||||||
switch (channel) {
|
switch (channel) {
|
||||||
case 'sms':
|
case 'sms':
|
||||||
if (rule.alert_channels.includes('sms') && rule.sms_phone_number) {
|
if (rule.alert_channels.includes('sms') && rule.sms_phone_number) {
|
||||||
alertLog = await this.sendSMSAlert(rule.sms_phone_number, message, rule, detection, threatAssessment);
|
alertLog = await this.sendSMSAlert(rule.sms_phone_number, message, rule, detection, threatAssessment, alertEventId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'email':
|
case 'email':
|
||||||
if (rule.alert_channels.includes('email') && (rule.email || user.email)) {
|
if (rule.alert_channels.includes('email') && (rule.email || user.email)) {
|
||||||
alertLog = await this.sendEmailAlert(rule.email || user.email, message, rule, detection, threatAssessment);
|
alertLog = await this.sendEmailAlert(rule.email || user.email, message, rule, detection, threatAssessment, alertEventId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'webhook':
|
case 'webhook':
|
||||||
if (rule.alert_channels.includes('webhook') && rule.webhook_url) {
|
if (rule.alert_channels.includes('webhook') && rule.webhook_url) {
|
||||||
alertLog = await this.sendWebhookAlert(rule.webhook_url, detection, device, rule, threatAssessment);
|
alertLog = await this.sendWebhookAlert(rule.webhook_url, detection, device, rule, threatAssessment, alertEventId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -482,7 +486,7 @@ class AlertService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (alertLog) {
|
if (alertLog) {
|
||||||
console.log(`🚨 ${threatAssessment.level.toUpperCase()} THREAT: Alert sent via ${channel} for detection ${detection.id}`);
|
console.log(`🚨 ${threatAssessment.level.toUpperCase()} THREAT: Alert sent via ${channel} for detection ${detection.id} (Event: ${alertEventId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (channelError) {
|
} catch (channelError) {
|
||||||
@@ -510,7 +514,7 @@ class AlertService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendSMSAlert(phoneNumber, message, rule, detection) {
|
async sendSMSAlert(phoneNumber, message, rule, detection, threatAssessment = null, alertEventId = null) {
|
||||||
// Check if Twilio is enabled
|
// Check if Twilio is enabled
|
||||||
if (!this.twilioEnabled || !this.twilioClient) {
|
if (!this.twilioEnabled || !this.twilioClient) {
|
||||||
console.log('📱 SMS alert skipped - Twilio not configured');
|
console.log('📱 SMS alert skipped - Twilio not configured');
|
||||||
@@ -527,7 +531,8 @@ class AlertService {
|
|||||||
sent_at: new Date(),
|
sent_at: new Date(),
|
||||||
external_id: null,
|
external_id: null,
|
||||||
priority: rule.priority,
|
priority: rule.priority,
|
||||||
error_message: 'SMS service not configured'
|
error_message: 'SMS service not configured',
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +557,8 @@ class AlertService {
|
|||||||
status: 'sent',
|
status: 'sent',
|
||||||
sent_at: new Date(),
|
sent_at: new Date(),
|
||||||
external_id: twilioMessage.sid,
|
external_id: twilioMessage.sid,
|
||||||
priority: rule.priority
|
priority: rule.priority,
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Failed to send SMS to ${phoneNumber}:`, error.message);
|
console.error(`❌ Failed to send SMS to ${phoneNumber}:`, error.message);
|
||||||
@@ -567,12 +573,13 @@ class AlertService {
|
|||||||
sent_at: new Date(),
|
sent_at: new Date(),
|
||||||
external_id: null,
|
external_id: null,
|
||||||
priority: rule.priority,
|
priority: rule.priority,
|
||||||
error_message: error.message
|
error_message: error.message,
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEmailAlert(email, message, rule, detection) {
|
async sendEmailAlert(email, message, rule, detection, threatAssessment = null, alertEventId = null) {
|
||||||
// Email implementation would go here
|
// Email implementation would go here
|
||||||
// For now, just log the alert
|
// For now, just log the alert
|
||||||
console.log(`Email alert would be sent to ${email}: ${message}`);
|
console.log(`Email alert would be sent to ${email}: ${message}`);
|
||||||
@@ -585,11 +592,12 @@ class AlertService {
|
|||||||
message: message,
|
message: message,
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
sent_at: new Date(),
|
sent_at: new Date(),
|
||||||
priority: rule.priority
|
priority: rule.priority,
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendWebhookAlert(webhookUrl, detection, device, rule) {
|
async sendWebhookAlert(webhookUrl, detection, device, rule, threatAssessment = null, alertEventId = null) {
|
||||||
const payload = {
|
const payload = {
|
||||||
event: 'drone_detection',
|
event: 'drone_detection',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -639,7 +647,8 @@ class AlertService {
|
|||||||
message: JSON.stringify(payload),
|
message: JSON.stringify(payload),
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
sent_at: new Date(),
|
sent_at: new Date(),
|
||||||
priority: rule.priority
|
priority: rule.priority,
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,7 +744,7 @@ class AlertService {
|
|||||||
|
|
||||||
console.log(`🟢 ALERT CLEARED - Sending clear notification for ${device.name} to ${rule.sms_phone_number}`);
|
console.log(`🟢 ALERT CLEARED - Sending clear notification for ${device.name} to ${rule.sms_phone_number}`);
|
||||||
|
|
||||||
await this.sendSMSAlert(rule.sms_phone_number, clearMessage, rule, detection, null);
|
await this.sendSMSAlert(rule.sms_phone_number, clearMessage, rule, detection, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,9 +830,10 @@ class AlertService {
|
|||||||
* @param {Object} detection - The detection that triggered the alert
|
* @param {Object} detection - The detection that triggered the alert
|
||||||
* @param {string} alertType - Type of alert (sms, email, etc.)
|
* @param {string} alertType - Type of alert (sms, email, etc.)
|
||||||
* @param {string} recipient - Alert recipient
|
* @param {string} recipient - Alert recipient
|
||||||
|
* @param {string} alertEventId - UUID to group related alerts from the same event
|
||||||
* @returns {Promise<Object>} - The created alert log entry
|
* @returns {Promise<Object>} - The created alert log entry
|
||||||
*/
|
*/
|
||||||
async logAlert(rule, detection, alertType = 'sms', recipient = 'test@example.com') {
|
async logAlert(rule, detection, alertType = 'sms', recipient = 'test@example.com', alertEventId = null) {
|
||||||
try {
|
try {
|
||||||
const alertLog = await AlertLog.create({
|
const alertLog = await AlertLog.create({
|
||||||
alert_rule_id: rule.id,
|
alert_rule_id: rule.id,
|
||||||
@@ -832,7 +842,8 @@ class AlertService {
|
|||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
message: `Alert triggered for ${rule.name}`,
|
message: `Alert triggered for ${rule.name}`,
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
sent_at: new Date()
|
sent_at: new Date(),
|
||||||
|
alert_event_id: alertEventId
|
||||||
});
|
});
|
||||||
|
|
||||||
return alertLog;
|
return alertLog;
|
||||||
@@ -853,15 +864,21 @@ class AlertService {
|
|||||||
const processedAlerts = [];
|
const processedAlerts = [];
|
||||||
|
|
||||||
for (const rule of triggeredRules) {
|
for (const rule of triggeredRules) {
|
||||||
|
// Generate unique event ID for this rule/detection combination
|
||||||
|
const alertEventId = uuidv4();
|
||||||
|
|
||||||
// Assess threat level
|
// Assess threat level
|
||||||
const threat = this.assessThreatLevel(detection.rssi, detection.drone_type);
|
const threat = this.assessThreatLevel(detection.rssi, detection.drone_type);
|
||||||
|
|
||||||
// Log the alert
|
// Log the alert
|
||||||
const alertLog = await this.logAlert(rule, detection);
|
const alertLog = await this.logAlert(rule, detection, 'sms', 'test@example.com', alertEventId);
|
||||||
|
|
||||||
// Send notification if threshold is met
|
// Send notification if threshold is met
|
||||||
if (threat.threatLevel === 'critical' || threat.threatLevel === 'high') {
|
if (threat.threatLevel === 'critical' || threat.threatLevel === 'high') {
|
||||||
await this.sendSMSAlert(detection, threat, rule);
|
// Need phone number for SMS - this might need to be updated based on rule configuration
|
||||||
|
const phoneNumber = rule.sms_phone_number || 'test-phone';
|
||||||
|
const message = `THREAT DETECTED: ${threat.description}`;
|
||||||
|
await this.sendSMSAlert(phoneNumber, message, rule, detection, threat, alertEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
processedAlerts.push({
|
processedAlerts.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user