const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const cookieParser = require('cookie-parser'); const mongoose = require('mongoose'); const config = require('./config/env'); const connectDB = require('./config/database'); const errorHandler = require('./middleware/errorHandler'); const requestLogger = require('./middleware/requestLogger'); const { apiLimiter, authLimiter } = require('./middleware/rateLimiter'); const logger = require('./utils/logger'); const { version } = require('./package.json'); // Connect to database with retry logic const connectWithRetry = async () => { try { await connectDB(); // Seed database if empty (runs in all environments on first start) const User = require('./models/User'); const userCount = await User.countDocuments(); if (userCount === 0) { logger.info('Datenbank ist leer, starte Seeding...'); try { const seed = require('./seed'); await seed(); } catch (seedError) { logger.error('Fehler beim Seeding:', seedError.message); } } } catch (error) { logger.error('Verbindungsversuch fehlgeschlagen, erneuter Versuch in 5 Sekunden...'); setTimeout(connectWithRetry, 5000); } }; connectWithRetry(); const app = express(); // Trust first proxy (nginx reverse proxy sets X-Forwarded-For) app.set('trust proxy', 1); // Security headers app.use(helmet({ crossOriginResourcePolicy: { policy: 'same-site' }, contentSecurityPolicy: false // Managed by nginx/frontend })); // Middleware app.use(cors({ origin: config.corsOrigin, credentials: true })); app.use(express.json({ limit: '2mb' })); app.use(express.urlencoded({ extended: true, limit: '2mb' })); app.use(cookieParser()); // Parse cookies app.use(requestLogger); // Rate limiting app.use('/api/', apiLimiter); // Global API rate limiter // Routes app.use('/api/auth', authLimiter, require('./routes/authRoutes')); // Strict limiter for auth app.use('/api', require('./routes/userRoutes')); app.use('/api', require('./routes/auditRoutes')); app.use('/api/config', require('./routes/configRoutes')); app.use('/api/drohnenfuehrer', require('./routes/drohnenfuehrerRoutes')); // Health check with basic system info app.get('/health', async (req, res) => { const dbStatus = mongoose.connection.readyState === 1 ? 'connected' : 'disconnected'; const isProd = config.nodeEnv === 'production'; res.json({ status: dbStatus === 'connected' ? 'OK' : 'DEGRADED', ...(isProd ? {} : { timestamp: new Date().toISOString(), uptime: process.uptime(), environment: config.nodeEnv, database: dbStatus, version }) }); }); // Error handler (must be last) app.use(errorHandler); const PORT = config.port; const server = app.listen(PORT, () => { logger.info(`Server läuft auf Port ${PORT} (${config.nodeEnv})`); }); // Graceful shutdown on SIGTERM (Docker stop / Kubernetes rolling restart) process.on('SIGTERM', () => { logger.info('SIGTERM empfangen, fahre Server herunter...'); server.close(() => { logger.info('HTTP-Server geschlossen'); mongoose.connection.close(false).then(() => { logger.info('MongoDB-Verbindung geschlossen'); process.exit(0); }).catch(() => process.exit(1)); }); }); // If a frontend build exists, serve it as static files (useful for local testing) const path = require('path'); const fs = require('fs'); const buildPath = path.join(__dirname, '..', 'frontend', 'build'); if (fs.existsSync(buildPath)) { logger.info('Frontend-Build gefunden — serviere statische Dateien von frontend/build'); app.use(express.static(buildPath)); // Serve index.html for any unknown GET route (SPA fallback) app.get('*', (req, res, next) => { if (req.path.startsWith('/api') || req.path === '/health') return next(); res.sendFile(path.join(buildPath, 'index.html')); }); }