Files
drone-detector/server/index.js
2025-09-23 14:03:24 +02:00

261 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const path = require('path');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const { createServer } = require('http');
const { Server } = require('socket.io');
require('dotenv').config();
const { sequelize } = require('./models');
const routes = require('./routes');
const { initializeSocketHandlers } = require('./services/socketService');
const AlertService = require('./services/alertService');
const DeviceHealthService = require('./services/deviceHealthService');
const { initializeHealthService } = require('./routes/deviceHealth');
const seedDatabase = require('./seedDatabase');
const errorHandler = require('./middleware/errorHandler');
const { apiDebugMiddleware } = require('./utils/apiDebugLogger');
const IPRestrictionMiddleware = require('./middleware/ip-restriction');
const { Umzug, SequelizeStorage } = require('umzug');
const app = express();
// Trust proxy headers for getting real client IPs behind nginx
// Trust only the first proxy (nginx) for security
app.set('trust proxy', 1);
const server = createServer(app);
const io = new Server(server, {
path: '/ws',
cors: {
origin: [
process.env.CORS_ORIGIN || "http://localhost:3000",
"https://selfservice.cqers.com",
"http://localhost:3001"
],
methods: ["GET", "POST", "PUT", "DELETE"]
}
});
// Rate limiting (exclude detections endpoint for testing)
const limiter = rateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 1000, // Increased from 100 to 1000
message: 'Too many requests from this IP, please try again later.',
// Custom key generator to handle proxy headers properly
keyGenerator: (req) => {
// Get the real IP from proxy headers, with fallback
return req.ip || req.connection.remoteAddress || 'unknown';
},
skip: (req) => {
// Skip rate limiting for drone detection endpoints during testing
return req.path.includes('/detections') || req.path.includes('/detectors');
},
// Skip failed requests (don't count them against the limit)
skipFailedRequests: true,
// Skip successful requests (only count errors/abuse)
skipSuccessfulRequests: false
});
// Middleware
app.use(helmet());
app.use(compression());
app.use(morgan('combined'));
app.use(cors({
origin: [
process.env.CORS_ORIGIN || "http://localhost:3000",
"https://selfservice.cqers.com",
"http://localhost:3001"
],
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Serve uploaded files
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// API Debug logging (only when API_DEBUG=true)
if (process.env.API_DEBUG === 'true') {
console.log('🐛 API Debug logging enabled');
app.use('/api', apiDebugMiddleware);
}
app.use('/api/', limiter);
// IP Restriction middleware (before routes)
const ipRestriction = new IPRestrictionMiddleware();
app.use((req, res, next) => ipRestriction.checkIPRestriction(req, res, next));
// Tenant-specific API rate limiting (for authenticated endpoints)
const { enforceApiRateLimit } = require('./middleware/tenant-limits');
app.use('/api', (req, res, next) => {
// Apply tenant rate limiting only to authenticated API endpoints
if (req.headers.authorization) {
return enforceApiRateLimit()(req, res, next);
}
next();
});
// Make io available to routes
app.use((req, res, next) => {
req.io = io;
next();
});
// Routes
app.use('/api', routes);
// Health check endpoints
app.get('/health', (req, res) => {
const response = {
status: 'OK',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV
};
// Log health check if debugging enabled
if (process.env.API_DEBUG === 'true') {
const { ApiDebugLogger } = require('./utils/apiDebugLogger');
const logger = new ApiDebugLogger();
logger.log('GET', '/health', 200, {}, response);
}
res.status(200).json(response);
});
app.use('/api/health', require('./routes/health'));
// Error handling
app.use(errorHandler);
// Socket.IO initialization
initializeSocketHandlers(io);
const PORT = process.env.PORT || 5000;
// Migration runner
const runMigrations = async () => {
const umzug = new Umzug({
migrations: {
glob: 'migrations/*.js',
resolve: ({ name, path }) => {
const migration = require(path);
return {
name,
up: async () => migration.up(sequelize.getQueryInterface(), require('sequelize')),
down: async () => migration.down && migration.down(sequelize.getQueryInterface(), require('sequelize'))
};
}
},
storage: new SequelizeStorage({ sequelize }),
logger: console,
});
console.log('Running database migrations...');
await umzug.up();
console.log('Migrations completed successfully.');
};
// Database connection and server startup
async function startServer() {
try {
await sequelize.authenticate();
console.log('Database connected successfully.');
// STEP 1: Sync database first to create base tables
try {
// Use alter: false to prevent destructive changes in production
await sequelize.sync({ force: false, alter: false });
console.log('Database synchronized.');
} catch (syncError) {
console.error('Database sync error:', syncError);
// If sync fails, try force sync (this will drop and recreate tables)
console.log('Attempting force sync...');
await sequelize.sync({ force: true });
console.log('Database force synchronized.');
}
// STEP 2: Run migrations after tables exist (skip if DB_INITIALIZED is set)
if (!process.env.DB_INITIALIZED) {
try {
await runMigrations();
} catch (migrationError) {
console.error('Migration error:', migrationError);
throw migrationError; // Fatal error - don't continue
}
// STEP 3: Seed database with initial data
try {
await seedDatabase();
} catch (seedError) {
console.error('Seeding error:', seedError);
throw seedError; // Fatal error - don't continue
}
} else {
console.log(' Database already initialized by setup script, skipping migrations and seeding');
}
server.listen(PORT, () => {
console.log('\n🚀 Drone Detection System Started Successfully!');
console.log('================================================');
console.log(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(`🌐 Server Port: ${PORT}`);
console.log(`💾 Database: ${process.env.DB_HOST}:${process.env.DB_PORT}`);
console.log(`🔴 Redis: ${process.env.REDIS_HOST || 'localhost'}:${process.env.REDIS_PORT || 6379}`);
// Initialize AlertService to check SMS status
const alertService = new AlertService();
if (alertService.twilioEnabled) {
console.log('📱 SMS Alerts: ✅ Enabled');
} else {
console.log('📱 SMS Alerts: ⚠️ Disabled (no Twilio credentials)');
}
// Start periodic clear alert checking (every 2 minutes)
setInterval(async () => {
try {
await alertService.checkClearedAlerts();
} catch (error) {
console.error('Error checking cleared alerts:', error);
}
}, 2 * 60 * 1000); // Check every 2 minutes
console.log('🔄 Clear alert monitoring: ✅ Started');
// Initialize Device Health Monitoring Service
const deviceHealthService = new DeviceHealthService();
initializeHealthService(deviceHealthService); // Make it available to API routes
deviceHealthService.start();
console.log('🏥 Device health monitoring: ✅ Started');
// Graceful shutdown for services
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
deviceHealthService.stop();
process.exit(0);
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
deviceHealthService.stop();
process.exit(0);
});
console.log(`📊 Health check: http://localhost:${PORT}/health`);
console.log(`🌐 API endpoint: http://localhost:${PORT}/api`);
console.log('================================================\n');
});
} catch (error) {
console.error('Unable to start server:', error);
process.exit(1);
}
}
startServer();
module.exports = { app, server, io };