const { createServer } = require('http') const { parse } = require('url') const next = require('next') const { Server } = require('socket.io') const dev = process.env.NODE_ENV !== 'production' const hostname = process.env.HOSTNAME || '0.0.0.0' const port = parseInt(process.env.PORT || '3000', 10) const app = next({ dev, hostname, port }) const handle = app.getRequestHandler() app.prepare().then(() => { const httpServer = createServer(async (req, res) => { try { const parsedUrl = parse(req.url, true) await handle(req, res, parsedUrl) } catch (err) { console.error('Error occurred handling', req.url, err) res.statusCode = 500 res.end('internal server error') } }) const io = new Server(httpServer, { cors: { origin: '*' }, path: '/socket.io', }) // Make io globally accessible for API routes global.__io = io io.on('connection', (socket) => { console.log(`[Socket.io] Client connected: ${socket.id}`) // Client joins a project room socket.on('join-project', (projectId) => { socket.join(`project:${projectId}`) console.log(`[Socket.io] ${socket.id} joined project:${projectId}`) }) // Client leaves a project room socket.on('leave-project', (projectId) => { socket.leave(`project:${projectId}`) console.log(`[Socket.io] ${socket.id} left project:${projectId}`) }) // Features updated — broadcast to all others in the project room socket.on('features-updated', (data) => { const { projectId, features } = data socket.to(`project:${projectId}`).emit('features-changed', { features }) }) // Editing lock changed — broadcast to all others in the project room socket.on('editing-changed', (data) => { const { projectId, editing, editingBy, sessionId } = data socket.to(`project:${projectId}`).emit('editing-status', { editing, editingBy, sessionId }) }) // Journal entry added/updated — broadcast to all others socket.on('journal-updated', (data) => { const { projectId } = data socket.to(`project:${projectId}`).emit('journal-changed', { projectId }) }) socket.on('disconnect', () => { console.log(`[Socket.io] Client disconnected: ${socket.id}`) }) }) httpServer.listen(port, hostname, () => { console.log(`> Ready on http://${hostname}:${port}`) }) // Backup-Scheduler: stündlich prüfen, ob laut Zeitplan ein Backup fällig ist. // Die Route /api/cron/backup entscheidet anhand der Konfiguration; hier wird nur getriggert. if (process.env.CRON_SECRET) { const checkBackup = () => { fetch(`http://127.0.0.1:${port}/api/cron/backup`, { method: 'POST', headers: { 'x-cron-secret': process.env.CRON_SECRET }, }) .then((r) => r.json()) .then((d) => { if (d && d.skipped !== true) console.log('[backup-scheduler]', JSON.stringify(d)) }) .catch((e) => console.warn('[backup-scheduler]', e.message)) } setInterval(checkBackup, 60 * 60 * 1000) // stündlich setTimeout(checkBackup, 90 * 1000) // ~1.5 Min nach Start einmal console.log('> Backup-Scheduler aktiv (stündliche Fälligkeitsprüfung)') } })