const express = require('express'); const cors = require('cors'); const config = require('./config'); const previewRoutes = require('./api/routes/preview-routes'); const orderRoutes = require('./api/routes/order-routes'); const chargeRoutes = require('./api/routes/charge-routes'); const batchRoutes = require('./api/routes/batch-routes'); const healthRoutes = require('./api/routes/health-routes'); const { runScheduledBatch, berlinParts } = require('./api/controllers/batch-controller'); const errorHandler = require('./api/middleware/error-handler'); const requestLogger = require('./api/middleware/request-logger'); const { applyRenderFormats, setFormatMissHandler } = require('./lib/page-layout'); const { fetchRenderFormats } = require('./services/directus-service'); // Zentrale Format-Definitionen aus Directus in den Cache holen. Läuft beim Start // und stündlich; Fehler sind unkritisch (die Defaults im page-layout greifen). let lastFormatRefresh = 0; async function refreshRenderFormats() { lastFormatRefresh = Date.now(); try { const rows = await fetchRenderFormats(); applyRenderFormats(rows); console.log(`[Formate] ${Array.isArray(rows) ? rows.length : 0} Format(e) aus Directus geladen.`); } catch (e) { console.warn(`[Formate] Konnte Directus-Formate nicht laden (nutze Defaults): ${e.message}`); } } // Unbekanntes Format angefragt → sofort nachladen (max. alle 5 s, damit ein // dauerhaft unbekanntes Format kein Dauerfeuer auslöst). Neu in Directus angelegte // Formate sind damit ohne Backend-Neustart binnen weniger Sekunden aktiv. setFormatMissHandler((key) => { if (Date.now() - lastFormatRefresh < 5 * 1000) return; console.log(`[Formate] Unbekanntes Format "${key}" angefragt – lade Formate nach.`); refreshRenderFormats(); }); const app = express(); // Middleware app.use(cors(config.cors)); app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); app.use(requestLogger); // Routes app.use('/health', healthRoutes); app.use('/api/preview', previewRoutes); app.use('/api/order', orderRoutes); app.use('/api/charge', chargeRoutes); app.use('/api/batch', batchRoutes); // Format-Cache manuell auffrischen (Token-geschützt) – für „sofort übernehmen". app.post('/api/formats/refresh', async (req, res) => { const token = config.auth && config.auth.apiToken; if (token && req.headers['x-api-token'] !== token) { return res.status(401).json({ error: 'API token required' }); } await refreshRenderFormats(); res.json({ ok: true }); }); // Error handling app.use(errorHandler); // Start server app.listen(config.port, () => { console.log('╔════════════════════════════════════════════════════════════╗'); console.log('║ Skrift Backend Server ║'); console.log('╚════════════════════════════════════════════════════════════╝'); console.log(`Environment: ${config.env}`); console.log(`Port: ${config.port}`); console.log(`Batch Size: Unlimited (auto-split to ${config.preview.scriptalizerBatchSize} for API)`); console.log(`Cache: Disabled`); console.log(`Rate Limit: Disabled`); console.log(`Scriptalizer: ${config.scriptalizer.licenseKey ? 'Configured ✓' : 'Missing ✗'}`); console.log('════════════════════════════════════════════════════════════'); console.log(`Server running at http://localhost:${config.port}`); console.log('════════════════════════════════════════════════════════════\n'); // Formate initial laden und stündlich auffrischen (zentral aus Directus). refreshRenderFormats(); setInterval(refreshRenderFormats, 60 * 60 * 1000); // Batch-FTP-Bündelung nur, wenn ausdrücklich aktiviert (Standardweg ist die // Übernahme in die Produktion, nicht FTP). SKRIFT_BATCH_FTP_CRON=true schaltet // den täglichen 03:00-Lauf (Europe/Berlin) wieder ein; Sa+So am Montag. if (String(process.env.SKRIFT_BATCH_FTP_CRON || '').toLowerCase() === 'true') { let letzterBatchTag = null; setInterval(() => { try { const { date, hour } = berlinParts(); if (hour !== 3 || letzterBatchTag === date) return; letzterBatchTag = date; runScheduledBatch().catch((e) => console.error('[batch-cron] Fehler:', e.message)); } catch (e) { console.error('[batch-cron] Tick-Fehler:', e.message); } }, 60 * 1000); console.log('Batch-FTP-Cron: aktiv (03:00 Europe/Berlin)'); } }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully...'); process.exit(0); }); process.on('SIGINT', () => { console.log('\nSIGINT received, shutting down gracefully...'); process.exit(0); });