import fs from 'node:fs'; import http from 'node:http'; import path from 'node:path'; import express from 'express'; import { REPO_ROOT } from './config.js'; import { openDatabase } from './db.js'; import { createApiRouter } from './http.js'; import { createDiscordNotifier } from './discord.js'; import { loadMapPools } from './mapPools.js'; import { MatchService } from './matchService.js'; import { attachRealtime } from './realtime.js'; import { createServerAdapter } from './servers/adapter.js'; import { ServerManager } from './servers/serverManager.js'; /** Baut die komplette Anwendung zusammen (auch für Tests nutzbar). */ export function createApp(config, { adapter: adapterOverride, logger = console } = {}) { const db = openDatabase(config.databaseFile); const pools = loadMapPools(config.mapPoolFile); const notifier = createDiscordNotifier(config.discordWebhookUrl, { logger }); const service = new MatchService({ db, pools, config, notifier }); let manager; const adapter = adapterOverride ?? createServerAdapter(config, { getUsedPorts: () => manager.usedPorts() }); manager = new ServerManager({ db, service, adapter, config, logger }); manager.init(); service.startVetoTimer(); const app = express(); if (config.trustProxy) app.set('trust proxy', 1); app.disable('x-powered-by'); app.use((req, res, next) => { res.set('X-Content-Type-Options', 'nosniff'); res.set('Referrer-Policy', 'no-referrer'); next(); }); app.use('/api', createApiRouter({ service, manager, config, pools, adapter })); // Gebautes Frontend ausliefern (npm run build), SPA-Fallback auf index.html. const dist = path.join(REPO_ROOT, 'client', 'dist'); if (fs.existsSync(dist)) { app.use(express.static(dist, { index: false, maxAge: '1h' })); app.get('*', (req, res) => res.sendFile(path.join(dist, 'index.html'))); } const server = http.createServer(app); const io = attachRealtime(server, { service, config }); return { app, server, io, db, service, manager, adapter, async start() { await manager.resume(); manager.startLoops(); await new Promise((resolve) => server.listen(config.port, resolve)); return server.address().port; }, async close() { manager.stopLoops(); service.stopVetoTimer(); // Laufende Server-Aktionen (Start, Konfiguration) kurz abwarten, bevor die Datenbank schließt await manager.idle(5000); io.disposeRealtime(); io.close(); await new Promise((resolve) => server.close(() => resolve())); db.close(); }, }; }