822 lines
22 KiB
JavaScript
822 lines
22 KiB
JavaScript
/**
|
|
* Initial Migration: Create all base tables
|
|
* This migration creates the core database structure
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
module.exports = {
|
|
async up(queryInterface, Sequelize) {
|
|
// Create tenants table first (referenced by other tables)
|
|
try {
|
|
await queryInterface.createTable('tenants', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true,
|
|
allowNull: false
|
|
},
|
|
name: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false,
|
|
comment: 'Organization or tenant name'
|
|
},
|
|
slug: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false,
|
|
unique: true,
|
|
comment: 'URL-friendly identifier'
|
|
},
|
|
domain: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Domain for SSO integration'
|
|
},
|
|
subdomain: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Subdomain for multi-tenant routing'
|
|
},
|
|
subscription_type: {
|
|
type: Sequelize.ENUM('free', 'basic', 'premium', 'enterprise'),
|
|
defaultValue: 'basic',
|
|
allowNull: false,
|
|
comment: 'Subscription tier of the tenant'
|
|
},
|
|
is_active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true,
|
|
comment: 'Whether tenant is active'
|
|
},
|
|
auth_provider: {
|
|
type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'),
|
|
defaultValue: 'local',
|
|
comment: 'Primary authentication provider'
|
|
},
|
|
auth_config: {
|
|
type: Sequelize.JSONB,
|
|
allowNull: true,
|
|
comment: 'Authentication provider configuration'
|
|
},
|
|
user_mapping: {
|
|
type: Sequelize.JSONB,
|
|
allowNull: true,
|
|
comment: 'User attribute mapping from external provider'
|
|
},
|
|
role_mapping: {
|
|
type: Sequelize.JSONB,
|
|
allowNull: true,
|
|
comment: 'Role mapping from external provider to internal roles'
|
|
},
|
|
branding: {
|
|
type: Sequelize.JSONB,
|
|
allowNull: true,
|
|
comment: 'Tenant-specific branding'
|
|
},
|
|
features: {
|
|
type: Sequelize.JSONB,
|
|
defaultValue: {
|
|
max_devices: 10,
|
|
max_users: 5,
|
|
api_rate_limit: 1000,
|
|
data_retention_days: 90,
|
|
features: ['basic_detection', 'alerts', 'dashboard']
|
|
},
|
|
comment: 'Tenant feature limits and enabled features'
|
|
},
|
|
admin_email: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Primary admin email for this tenant'
|
|
},
|
|
admin_phone: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Primary admin phone for this tenant'
|
|
},
|
|
billing_email: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
payment_method_id: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Payment provider customer ID'
|
|
},
|
|
metadata: {
|
|
type: Sequelize.JSONB,
|
|
allowNull: true,
|
|
comment: 'Additional tenant metadata'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
console.log('✅ Created tenants table');
|
|
} catch (error) {
|
|
if (error.parent?.code === '42P07') { // Table already exists
|
|
console.log('⚠️ Tenants table already exists, skipping...');
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Create users table
|
|
await queryInterface.createTable('users', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true,
|
|
allowNull: false
|
|
},
|
|
username: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false,
|
|
unique: true
|
|
},
|
|
email: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
validate: {
|
|
isEmail: true
|
|
}
|
|
},
|
|
password_hash: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false
|
|
},
|
|
first_name: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
last_name: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
phone_number: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Phone number for SMS alerts (include country code)'
|
|
},
|
|
role: {
|
|
type: Sequelize.ENUM('admin', 'operator', 'viewer'),
|
|
defaultValue: 'viewer',
|
|
allowNull: false
|
|
},
|
|
external_provider: {
|
|
type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'),
|
|
defaultValue: 'local',
|
|
comment: 'Authentication provider used for this user'
|
|
},
|
|
external_id: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'User ID from external authentication provider'
|
|
},
|
|
sms_alerts_enabled: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
comment: 'Whether user wants to receive SMS alerts'
|
|
},
|
|
is_active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true,
|
|
comment: 'Whether user is active'
|
|
},
|
|
email_alerts_enabled: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true,
|
|
comment: 'Whether user wants to receive email alerts'
|
|
},
|
|
last_login: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true
|
|
},
|
|
timezone: {
|
|
type: Sequelize.STRING,
|
|
defaultValue: 'UTC',
|
|
comment: 'User timezone for alert scheduling'
|
|
},
|
|
tenant_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'tenants',
|
|
key: 'id'
|
|
},
|
|
onUpdate: 'CASCADE',
|
|
onDelete: 'SET NULL'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
allowNull: false
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
allowNull: false
|
|
}
|
|
});
|
|
|
|
// Create devices table
|
|
await queryInterface.createTable('devices', {
|
|
id: {
|
|
type: Sequelize.STRING(255),
|
|
primaryKey: true,
|
|
allowNull: false,
|
|
comment: 'Unique device identifier'
|
|
},
|
|
name: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Human-readable device name'
|
|
},
|
|
geo_lat: {
|
|
type: Sequelize.DECIMAL(10, 8),
|
|
allowNull: true,
|
|
comment: 'Device latitude coordinate'
|
|
},
|
|
geo_lon: {
|
|
type: Sequelize.DECIMAL(11, 8),
|
|
allowNull: true,
|
|
comment: 'Device longitude coordinate'
|
|
},
|
|
location_description: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: true,
|
|
comment: 'Human-readable location description'
|
|
},
|
|
is_active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true,
|
|
comment: 'Whether the device is currently active'
|
|
},
|
|
last_heartbeat: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true,
|
|
comment: 'Timestamp of last heartbeat received'
|
|
},
|
|
heartbeat_interval: {
|
|
type: Sequelize.INTEGER,
|
|
defaultValue: 300,
|
|
comment: 'Expected heartbeat interval in seconds'
|
|
},
|
|
firmware_version: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Device firmware version'
|
|
},
|
|
installation_date: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true,
|
|
comment: 'When the device was installed'
|
|
},
|
|
notes: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: true,
|
|
comment: 'Additional notes about the device'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
allowNull: false
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
allowNull: false
|
|
}
|
|
});
|
|
|
|
// Create heartbeats table
|
|
await queryInterface.createTable('heartbeats', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
device_id: {
|
|
type: Sequelize.STRING(255),
|
|
allowNull: false,
|
|
references: {
|
|
model: 'devices',
|
|
key: 'id'
|
|
},
|
|
comment: 'ID of the device sending heartbeat'
|
|
},
|
|
tenant_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true, // Nullable for backward compatibility
|
|
references: {
|
|
model: 'tenants',
|
|
key: 'id'
|
|
},
|
|
},
|
|
device_key: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
defaultValue: 'test-device-key',
|
|
comment: 'Unique key of the sensor from heartbeat message'
|
|
},
|
|
status: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Device status (online, offline, error, etc.)'
|
|
},
|
|
timestamp: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true,
|
|
comment: 'Timestamp from device'
|
|
},
|
|
uptime: {
|
|
type: Sequelize.BIGINT,
|
|
allowNull: true,
|
|
comment: 'Device uptime in seconds'
|
|
},
|
|
memory_usage: {
|
|
type: Sequelize.FLOAT,
|
|
allowNull: true,
|
|
comment: 'Memory usage percentage'
|
|
},
|
|
cpu_usage: {
|
|
type: Sequelize.FLOAT,
|
|
allowNull: true,
|
|
comment: 'CPU usage percentage'
|
|
},
|
|
disk_usage: {
|
|
type: Sequelize.FLOAT,
|
|
allowNull: true,
|
|
comment: 'Disk usage percentage'
|
|
},
|
|
firmware_version: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Firmware version reported in heartbeat'
|
|
},
|
|
received_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
comment: 'When heartbeat was received by server'
|
|
},
|
|
raw_payload: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Complete raw payload received from detector (for debugging)'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create drone_detections table
|
|
await queryInterface.createTable('drone_detections', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
device_id: {
|
|
type: Sequelize.STRING(255),
|
|
allowNull: false,
|
|
references: {
|
|
model: 'devices',
|
|
key: 'id'
|
|
},
|
|
comment: 'ID of the detecting device'
|
|
},
|
|
drone_id: {
|
|
type: Sequelize.BIGINT,
|
|
allowNull: false,
|
|
comment: 'ID of the detected drone'
|
|
},
|
|
drone_type: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Type of drone detected'
|
|
},
|
|
rssi: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Signal strength in dBm'
|
|
},
|
|
freq: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Frequency detected'
|
|
},
|
|
geo_lat: {
|
|
type: Sequelize.DECIMAL(10, 8),
|
|
allowNull: true,
|
|
comment: 'Latitude where detection occurred'
|
|
},
|
|
geo_lon: {
|
|
type: Sequelize.DECIMAL(11, 8),
|
|
allowNull: true,
|
|
comment: 'Longitude where detection occurred'
|
|
},
|
|
device_timestamp: {
|
|
type: Sequelize.BIGINT,
|
|
allowNull: true,
|
|
comment: 'Unix timestamp from the device'
|
|
},
|
|
server_timestamp: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW,
|
|
comment: 'When the detection was received by server'
|
|
},
|
|
confidence_level: {
|
|
type: Sequelize.DECIMAL(3, 2),
|
|
allowNull: true,
|
|
comment: 'Confidence level of detection (0.00-1.00)'
|
|
},
|
|
signal_duration: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Duration of signal in milliseconds'
|
|
},
|
|
processed: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
comment: 'Whether this detection has been processed for alerts'
|
|
},
|
|
threat_level: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Assessed threat level based on RSSI and drone type'
|
|
},
|
|
estimated_distance: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Estimated distance to drone in meters'
|
|
},
|
|
requires_action: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
comment: 'Whether this detection requires immediate security action'
|
|
},
|
|
raw_payload: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Complete raw payload received from detector (for debugging)'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create alert_rules table
|
|
await queryInterface.createTable('alert_rules', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
tenant_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'tenants',
|
|
key: 'id'
|
|
}
|
|
},
|
|
user_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: false,
|
|
references: {
|
|
model: 'users',
|
|
key: 'id'
|
|
}
|
|
},
|
|
name: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false
|
|
},
|
|
description: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: true
|
|
},
|
|
conditions: {
|
|
type: Sequelize.JSON,
|
|
allowNull: false
|
|
},
|
|
actions: {
|
|
type: Sequelize.JSON,
|
|
allowNull: false
|
|
},
|
|
device_ids: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Array of device IDs to monitor (null = all devices)'
|
|
},
|
|
drone_types: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Array of drone types to alert on (null = all types)'
|
|
},
|
|
min_rssi: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Minimum RSSI threshold for alert'
|
|
},
|
|
max_rssi: {
|
|
type: Sequelize.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Maximum RSSI threshold for alert'
|
|
},
|
|
frequency_ranges: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Array of frequency ranges to monitor [{min: 20, max: 30}]'
|
|
},
|
|
time_window: {
|
|
type: Sequelize.INTEGER,
|
|
defaultValue: 300,
|
|
comment: 'Time window in seconds to group detections'
|
|
},
|
|
min_detections: {
|
|
type: Sequelize.INTEGER,
|
|
defaultValue: 1,
|
|
comment: 'Minimum number of detections in time window to trigger alert'
|
|
},
|
|
cooldown_period: {
|
|
type: Sequelize.INTEGER,
|
|
defaultValue: 600,
|
|
comment: 'Cooldown period in seconds between alerts for same drone'
|
|
},
|
|
alert_channels: {
|
|
type: Sequelize.JSON,
|
|
defaultValue: ['sms'],
|
|
comment: 'Array of alert channels: sms, email, webhook'
|
|
},
|
|
sms_phone_number: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Phone number for SMS alerts'
|
|
},
|
|
webhook_url: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true,
|
|
comment: 'Webhook URL for custom integrations'
|
|
},
|
|
active_hours: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
comment: 'Active hours for alerts {start: "09:00", end: "17:00"}'
|
|
},
|
|
active_days: {
|
|
type: Sequelize.JSON,
|
|
defaultValue: [1, 2, 3, 4, 5, 6, 7],
|
|
comment: 'Active days of week (1=Monday, 7=Sunday)'
|
|
},
|
|
priority: {
|
|
type: Sequelize.ENUM('low', 'medium', 'high', 'critical'),
|
|
defaultValue: 'medium',
|
|
comment: 'Alert priority level'
|
|
},
|
|
min_threat_level: {
|
|
type: Sequelize.ENUM('monitoring', 'low', 'medium', 'high', 'critical'),
|
|
allowNull: true,
|
|
comment: 'Minimum threat level required to trigger alert'
|
|
},
|
|
|
|
is_active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create alert_logs table
|
|
await queryInterface.createTable('alert_logs', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
alert_rule_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'alert_rules',
|
|
key: 'id'
|
|
}
|
|
},
|
|
detection_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'drone_detections',
|
|
key: 'id'
|
|
}
|
|
},
|
|
device_id: {
|
|
type: Sequelize.STRING(255),
|
|
allowNull: true,
|
|
references: {
|
|
model: 'devices',
|
|
key: 'id'
|
|
}
|
|
},
|
|
alert_type: {
|
|
type: Sequelize.ENUM('sms', 'email', 'webhook', 'push'),
|
|
allowNull: true,
|
|
defaultValue: 'sms'
|
|
},
|
|
recipient: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
message: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: false
|
|
},
|
|
status: {
|
|
type: Sequelize.ENUM('pending', 'sent', 'failed', 'delivered'),
|
|
defaultValue: 'pending'
|
|
},
|
|
sent_at: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true
|
|
},
|
|
delivered_at: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true
|
|
},
|
|
error_message: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: true
|
|
},
|
|
external_id: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
cost: {
|
|
type: Sequelize.DECIMAL(10, 4),
|
|
allowNull: true
|
|
},
|
|
retry_count: {
|
|
type: Sequelize.INTEGER,
|
|
defaultValue: 0
|
|
},
|
|
priority: {
|
|
type: Sequelize.ENUM('low', 'normal', 'high', 'urgent'),
|
|
defaultValue: 'normal'
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create AuditLogs table
|
|
await queryInterface.createTable('audit_logs', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
tenant_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'tenants',
|
|
key: 'id'
|
|
}
|
|
},
|
|
user_id: {
|
|
type: Sequelize.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'users',
|
|
key: 'id'
|
|
}
|
|
},
|
|
action: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false
|
|
},
|
|
resource_type: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
resource_id: {
|
|
type: Sequelize.STRING,
|
|
allowNull: true
|
|
},
|
|
details: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true
|
|
},
|
|
ip_address: {
|
|
type: Sequelize.INET,
|
|
allowNull: true
|
|
},
|
|
user_agent: {
|
|
type: Sequelize.TEXT,
|
|
allowNull: true
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create management_users table
|
|
await queryInterface.createTable('management_users', {
|
|
id: {
|
|
type: Sequelize.UUID,
|
|
defaultValue: Sequelize.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
username: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false,
|
|
unique: true
|
|
},
|
|
email: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false,
|
|
unique: true,
|
|
validate: {
|
|
isEmail: true
|
|
}
|
|
},
|
|
password_hash: {
|
|
type: Sequelize.STRING,
|
|
allowNull: false
|
|
},
|
|
role: {
|
|
type: Sequelize.ENUM('super_admin', 'tenant_admin'),
|
|
defaultValue: 'tenant_admin',
|
|
allowNull: false
|
|
},
|
|
permissions: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
defaultValue: []
|
|
},
|
|
is_active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: true
|
|
},
|
|
last_login: {
|
|
type: Sequelize.DATE,
|
|
allowNull: true
|
|
},
|
|
created_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
},
|
|
updated_at: {
|
|
type: Sequelize.DATE,
|
|
defaultValue: Sequelize.NOW
|
|
}
|
|
});
|
|
|
|
// Create basic indexes
|
|
await queryInterface.addIndex('devices', ['geo_lat', 'geo_lon']);
|
|
await queryInterface.addIndex('devices', ['is_active']);
|
|
await queryInterface.addIndex('heartbeats', ['device_id']);
|
|
await queryInterface.addIndex('heartbeats', ['received_at']);
|
|
await queryInterface.addIndex('drone_detections', ['device_id']);
|
|
await queryInterface.addIndex('drone_detections', ['drone_id']);
|
|
await queryInterface.addIndex('drone_detections', ['server_timestamp']);
|
|
await queryInterface.addIndex('alert_rules', ['user_id']);
|
|
await queryInterface.addIndex('alert_logs', ['alert_rule_id']);
|
|
await queryInterface.addIndex('audit_logs', ['tenant_id']);
|
|
await queryInterface.addIndex('audit_logs', ['user_id']);
|
|
await queryInterface.addIndex('audit_logs', ['created_at']);
|
|
},
|
|
|
|
async down(queryInterface, Sequelize) {
|
|
// Drop tables in reverse order due to foreign key constraints
|
|
await queryInterface.dropTable('audit_logs');
|
|
await queryInterface.dropTable('management_users');
|
|
await queryInterface.dropTable('alert_logs');
|
|
await queryInterface.dropTable('alert_rules');
|
|
await queryInterface.dropTable('drone_detections');
|
|
await queryInterface.dropTable('heartbeats');
|
|
await queryInterface.dropTable('devices');
|
|
await queryInterface.dropTable('users');
|
|
await queryInterface.dropTable('tenants');
|
|
}
|
|
}; |