/** * NinjaCross Leaderboard Server * * Hauptserver für das NinjaCross Timer-System mit: * - Express.js Web-Server * - Socket.IO für Real-time Updates * - PostgreSQL Datenbankanbindung * - API-Key Authentifizierung * - Session-basierte Web-Authentifizierung * * @author NinjaCross Team * @version 1.0.0 */ // ============================================================================ // DEPENDENCIES & IMPORTS // ============================================================================ const express = require('express'); const path = require('path'); const session = require('express-session'); const { createServer } = require('http'); const { Server } = require('socket.io'); require('dotenv').config(); // Route Imports const { router: apiRoutes, requireApiKey } = require('./routes/api'); const publicRoutes = require('./routes/public'); // ============================================================================ // SERVER CONFIGURATION // ============================================================================ const app = express(); const server = createServer(app); const port = process.env.PORT || 3000; // Socket.IO Configuration const io = new Server(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); // ============================================================================ // MIDDLEWARE SETUP // ============================================================================ // Body Parser Middleware app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Session Configuration app.use(session({ secret: process.env.SESSION_SECRET || 'kjhdizr3lhwho8fpjslgf825ß0hsd', resave: false, saveUninitialized: false, cookie: { secure: false, // Set to true when using HTTPS maxAge: 24 * 60 * 60 * 1000, // 24 hours httpOnly: true // Security: prevent XSS attacks } })); // ============================================================================ // AUTHENTICATION MIDDLEWARE // ============================================================================ /** * Web Interface Authentication Middleware * Überprüft ob der Benutzer für das Web-Interface authentifiziert ist */ function requireWebAuth(req, res, next) { if (req.session.userId) { next(); } else { res.redirect('/login'); } } // ============================================================================ // ROUTE SETUP // ============================================================================ // Public API Routes (no authentication required) // Diese Routen sind für das Frontend-Leaderboard gedacht app.use('/public-api', publicRoutes); // Private API Routes (API-Key authentication required) // Diese Routen sind für die Timer-Geräte und Admin-Interface app.use('/api', apiRoutes); // ============================================================================ // WEB INTERFACE ROUTES // ============================================================================ /** * Public Landing Page - NinjaCross Leaderboard * Hauptseite mit dem öffentlichen Leaderboard */ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); /** * Admin Dashboard Page * Hauptdashboard für Admin-Benutzer */ app.get('/admin-dashboard', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'admin-dashboard.html')); }); /** * Admin Generator Page * Geschützte Seite für die Lizenz-Generierung (Level 2 Zugriff erforderlich) */ app.get('/generator', requireWebAuth, (req, res) => { // Prüfe Zugriffslevel für Generator if (req.session.accessLevel < 2) { return res.status(403).send(`
Sie benötigen Level 2 Zugriff für den Lizenzgenerator.
Zurück zum Dashboard `); } res.sendFile(path.join(__dirname, 'public', 'generator.html')); }); /** * Login Page * Authentifizierungsseite für Admin-Benutzer */ app.get('/login', (req, res) => { // Redirect to main page if already authenticated if (req.session.userId) { return res.redirect('/'); } res.sendFile(path.join(__dirname, 'public', 'login.html')); }); /** * Admin Dashboard Page * Dashboard-Seite für eingeloggte Administratoren * Authentifizierung wird client-side über Supabase gehandhabt */ app.get('/dashboard', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'dashboard.html')); }); /** * Reset Password Page * Seite für das Zurücksetzen von Passwörtern über Supabase * Wird von Supabase E-Mail-Links aufgerufen */ app.get('/reset-password.html', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'reset-password.html')); }); /** * Admin Login Page * Lizenzgenerator Login-Seite für Admin-Benutzer */ app.get('/adminlogin.html', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'adminlogin.html')); }); // ============================================================================ // STATIC FILE SERVING // ============================================================================ // Serve static files directly from public directory app.use(express.static('public')); // Serve static files for public pages (CSS, JS, images) - legacy route app.use('/public', express.static('public')); // Serve static files for login page app.use('/login', express.static('public')); // ============================================================================ // WEBSOCKET CONFIGURATION // ============================================================================ /** * WebSocket Connection Handler * Verwaltet Real-time Verbindungen für Live-Updates */ io.on('connection', (socket) => { // Client connected - connection is established socket.on('disconnect', () => { // Client disconnected - cleanup if needed }); }); // Make Socket.IO instance available to other modules app.set('io', io); // ============================================================================ // ERROR HANDLING // ============================================================================ // 404 Handler app.use('*', (req, res) => { res.status(404).json({ success: false, message: 'Route not found', path: req.originalUrl }); }); // Global Error Handler app.use((err, req, res, next) => { console.error('Server Error:', err); res.status(500).json({ success: false, message: 'Internal server error' }); }); // ============================================================================ // SERVER STARTUP // ============================================================================ /** * Start the server and initialize all services */ server.listen(port, () => { console.log(`🚀 Server läuft auf http://ninja.reptilfpv.de:${port}`); console.log(`📊 Datenbank: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`); console.log(`🔐 API-Key Authentifizierung aktiviert`); console.log(`🔌 WebSocket-Server aktiviert`); console.log(`📁 Static files: /public`); console.log(`🌐 Public API: /public-api`); console.log(`🔑 Private API: /api`); }); // ============================================================================ // GRACEFUL SHUTDOWN // ============================================================================ /** * Handle graceful shutdown on SIGINT (Ctrl+C) */ process.on('SIGINT', async () => { console.log('\n🛑 Server wird heruntergefahren...'); // Close server gracefully server.close(() => { console.log('✅ Server erfolgreich heruntergefahren'); process.exit(0); }); // Force exit after 5 seconds if graceful shutdown fails setTimeout(() => { console.log('⚠️ Forced shutdown after timeout'); process.exit(1); }, 5000); }); /** * Handle uncaught exceptions */ process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err); process.exit(1); }); /** * Handle unhandled promise rejections */ process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); process.exit(1); });