Initial commit

This commit is contained in:
2025-08-16 19:43:44 +02:00
commit ea9a2627b4
64 changed files with 9232 additions and 0 deletions

109
server/models/AlertLog.js Normal file
View File

@@ -0,0 +1,109 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const AlertLog = sequelize.define('AlertLog', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
alert_rule_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'alert_rules',
key: 'id'
}
},
detection_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'drone_detections',
key: 'id'
}
},
alert_type: {
type: DataTypes.ENUM('sms', 'email', 'webhook', 'push'),
allowNull: false
},
recipient: {
type: DataTypes.STRING,
allowNull: false,
comment: 'Phone number, email, or webhook URL'
},
message: {
type: DataTypes.TEXT,
allowNull: false,
comment: 'Alert message content'
},
status: {
type: DataTypes.ENUM('pending', 'sent', 'failed', 'delivered'),
defaultValue: 'pending'
},
sent_at: {
type: DataTypes.DATE,
allowNull: true
},
delivered_at: {
type: DataTypes.DATE,
allowNull: true
},
error_message: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'Error message if alert failed'
},
external_id: {
type: DataTypes.STRING,
allowNull: true,
comment: 'External service message ID (Twilio SID, etc.)'
},
cost: {
type: DataTypes.DECIMAL(6, 4),
allowNull: true,
comment: 'Cost of sending the alert (for SMS, etc.)'
},
retry_count: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Number of retry attempts'
},
priority: {
type: DataTypes.ENUM('low', 'medium', 'high', 'critical'),
defaultValue: 'medium'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'alert_logs',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
fields: ['alert_rule_id']
},
{
fields: ['detection_id']
},
{
fields: ['status']
},
{
fields: ['sent_at']
},
{
fields: ['alert_type', 'status']
}
]
});
return AlertLog;
};

124
server/models/AlertRule.js Normal file
View File

@@ -0,0 +1,124 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const AlertRule = sequelize.define('AlertRule', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
name: {
type: DataTypes.STRING,
allowNull: false,
comment: 'Human-readable name for the alert rule'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'Description of what triggers this alert'
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
device_ids: {
type: DataTypes.JSON,
allowNull: true,
comment: 'Array of device IDs to monitor (null = all devices)'
},
drone_types: {
type: DataTypes.JSON,
allowNull: true,
comment: 'Array of drone types to alert on (null = all types)'
},
min_rssi: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Minimum RSSI threshold for alert'
},
max_rssi: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Maximum RSSI threshold for alert'
},
frequency_ranges: {
type: DataTypes.JSON,
allowNull: true,
comment: 'Array of frequency ranges to monitor [{min: 20, max: 30}]'
},
time_window: {
type: DataTypes.INTEGER,
defaultValue: 300,
comment: 'Time window in seconds to group detections'
},
min_detections: {
type: DataTypes.INTEGER,
defaultValue: 1,
comment: 'Minimum number of detections in time window to trigger alert'
},
cooldown_period: {
type: DataTypes.INTEGER,
defaultValue: 600,
comment: 'Cooldown period in seconds between alerts for same drone'
},
alert_channels: {
type: DataTypes.JSON,
defaultValue: ['sms'],
comment: 'Array of alert channels: sms, email, webhook'
},
webhook_url: {
type: DataTypes.STRING,
allowNull: true,
comment: 'Webhook URL for custom integrations'
},
active_hours: {
type: DataTypes.JSON,
allowNull: true,
comment: 'Active hours for alerts {start: "09:00", end: "17:00"}'
},
active_days: {
type: DataTypes.JSON,
defaultValue: [1, 2, 3, 4, 5, 6, 7],
comment: 'Active days of week (1=Monday, 7=Sunday)'
},
priority: {
type: DataTypes.ENUM('low', 'medium', 'high', 'critical'),
defaultValue: 'medium',
comment: 'Alert priority level'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'alert_rules',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
fields: ['user_id']
},
{
fields: ['is_active']
},
{
fields: ['priority']
}
]
});
return AlertRule;
};

88
server/models/Device.js Normal file
View File

@@ -0,0 +1,88 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Device = sequelize.define('Device', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
comment: 'Unique device identifier from hardware'
},
name: {
type: DataTypes.STRING,
allowNull: true,
comment: 'Human-readable device name'
},
geo_lat: {
type: DataTypes.DECIMAL(10, 8),
allowNull: true,
comment: 'Device latitude coordinate'
},
geo_lon: {
type: DataTypes.DECIMAL(11, 8),
allowNull: true,
comment: 'Device longitude coordinate'
},
location_description: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'Human-readable location description'
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Whether the device is currently active'
},
last_heartbeat: {
type: DataTypes.DATE,
allowNull: true,
comment: 'Timestamp of last heartbeat received'
},
heartbeat_interval: {
type: DataTypes.INTEGER,
defaultValue: 300, // 5 minutes
comment: 'Expected heartbeat interval in seconds'
},
firmware_version: {
type: DataTypes.STRING,
allowNull: true,
comment: 'Device firmware version'
},
installation_date: {
type: DataTypes.DATE,
allowNull: true,
comment: 'When the device was installed'
},
notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'Additional notes about the device'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'devices',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
fields: ['geo_lat', 'geo_lon']
},
{
fields: ['last_heartbeat']
},
{
fields: ['is_active']
}
]
});
return Device;
};

View File

@@ -0,0 +1,120 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const DroneDetection = sequelize.define('DroneDetection', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
device_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'devices',
key: 'id'
},
comment: 'ID of the detecting device'
},
drone_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Detected drone identifier'
},
drone_type: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: 'Type of drone detected (0=unknown, 1=commercial, 2=racing, etc.)'
},
rssi: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: 'Received Signal Strength Indicator'
},
freq: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Frequency detected'
},
geo_lat: {
type: DataTypes.DECIMAL(10, 8),
allowNull: true,
comment: 'Latitude where detection occurred'
},
geo_lon: {
type: DataTypes.DECIMAL(11, 8),
allowNull: true,
comment: 'Longitude where detection occurred'
},
device_timestamp: {
type: DataTypes.BIGINT,
allowNull: true,
comment: 'Unix timestamp from the device'
},
server_timestamp: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: 'When the detection was received by server'
},
confidence_level: {
type: DataTypes.DECIMAL(3, 2),
allowNull: true,
comment: 'Confidence level of detection (0.00-1.00)'
},
signal_duration: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Duration of signal in milliseconds'
},
processed: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Whether this detection has been processed for alerts'
},
threat_level: {
type: DataTypes.ENUM('monitoring', 'low', 'medium', 'high', 'critical'),
allowNull: true,
comment: 'Assessed threat level based on RSSI and drone type'
},
estimated_distance: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Estimated distance to drone in meters'
},
requires_action: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Whether this detection requires immediate security action'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'drone_detections',
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
indexes: [
{
fields: ['device_id']
},
{
fields: ['drone_id']
},
{
fields: ['server_timestamp']
},
{
fields: ['processed']
},
{
fields: ['device_id', 'drone_id', 'server_timestamp']
}
]
});
return DroneDetection;
};

View File

@@ -0,0 +1,82 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Heartbeat = sequelize.define('Heartbeat', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
device_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'devices',
key: 'id'
},
comment: 'ID of the device sending heartbeat'
},
device_key: {
type: DataTypes.STRING,
allowNull: false,
comment: 'Unique key of the sensor from heartbeat message'
},
signal_strength: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Signal strength at time of heartbeat'
},
battery_level: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Battery level percentage (0-100)'
},
temperature: {
type: DataTypes.DECIMAL(4, 1),
allowNull: true,
comment: 'Device temperature in Celsius'
},
uptime: {
type: DataTypes.BIGINT,
allowNull: true,
comment: 'Device uptime in seconds'
},
memory_usage: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'Memory usage percentage'
},
firmware_version: {
type: DataTypes.STRING,
allowNull: true,
comment: 'Firmware version reported in heartbeat'
},
received_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: 'When heartbeat was received by server'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'heartbeats',
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
indexes: [
{
fields: ['device_id']
},
{
fields: ['received_at']
},
{
fields: ['device_id', 'received_at']
}
]
});
return Heartbeat;
};

98
server/models/User.js Normal file
View File

@@ -0,0 +1,98 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
len: [3, 50]
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password_hash: {
type: DataTypes.STRING,
allowNull: false
},
first_name: {
type: DataTypes.STRING,
allowNull: true
},
last_name: {
type: DataTypes.STRING,
allowNull: true
},
phone_number: {
type: DataTypes.STRING,
allowNull: true,
comment: 'Phone number for SMS alerts (include country code)'
},
role: {
type: DataTypes.ENUM('admin', 'operator', 'viewer'),
defaultValue: 'viewer',
comment: 'User role for permission management'
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
sms_alerts_enabled: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Whether user wants to receive SMS alerts'
},
email_alerts_enabled: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Whether user wants to receive email alerts'
},
last_login: {
type: DataTypes.DATE,
allowNull: true
},
timezone: {
type: DataTypes.STRING,
defaultValue: 'UTC',
comment: 'User timezone for alert scheduling'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'users',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
fields: ['email']
},
{
fields: ['username']
},
{
fields: ['phone_number']
}
]
});
return User;
};

54
server/models/index.js Normal file
View File

@@ -0,0 +1,54 @@
const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(
process.env.DB_NAME || 'drone_detection',
process.env.DB_USER || 'postgres',
process.env.DB_PASSWORD || 'password',
{
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
dialect: 'postgres',
logging: process.env.NODE_ENV === 'development' ? console.log : false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
);
// Import models
const Device = require('./Device')(sequelize);
const DroneDetection = require('./DroneDetection')(sequelize);
const Heartbeat = require('./Heartbeat')(sequelize);
const User = require('./User')(sequelize);
const AlertRule = require('./AlertRule')(sequelize);
const AlertLog = require('./AlertLog')(sequelize);
// Define associations
Device.hasMany(DroneDetection, { foreignKey: 'device_id', as: 'detections' });
DroneDetection.belongsTo(Device, { foreignKey: 'device_id', as: 'device' });
Device.hasMany(Heartbeat, { foreignKey: 'device_id', as: 'heartbeats' });
Heartbeat.belongsTo(Device, { foreignKey: 'device_id', as: 'device' });
User.hasMany(AlertRule, { foreignKey: 'user_id', as: 'alertRules' });
AlertRule.belongsTo(User, { foreignKey: 'user_id', as: 'user' });
AlertRule.hasMany(AlertLog, { foreignKey: 'alert_rule_id', as: 'logs' });
AlertLog.belongsTo(AlertRule, { foreignKey: 'alert_rule_id', as: 'rule' });
DroneDetection.hasMany(AlertLog, { foreignKey: 'detection_id', as: 'alerts' });
AlertLog.belongsTo(DroneDetection, { foreignKey: 'detection_id', as: 'detection' });
module.exports = {
sequelize,
Device,
DroneDetection,
Heartbeat,
User,
AlertRule,
AlertLog
};