import express from 'express'; import bodyParser from 'body-parser'; import path from 'path'; import { fileURLToPath } from 'url'; // <-- REQUIRED FOR ESM import { ServerManager } from './ServerManager.js'; // NEW imports required for log streaming import { logsDir } from './paths.js'; import { existsSync, readFileSync } from 'node:fs'; import fs from 'node:fs'; // Recreate __dirname for ESM const __filename = fileURLToPath(import.meta.url); // <-- REQUIRED const __dirname = path.dirname(__filename); // <-- REQUIRED const DEFAULT_APP_ID = Number(process.env.WINDROSE_APP_ID || '4129620'); export function createApi() { const app = express(); const manager = new ServerManager(DEFAULT_APP_ID); // --------------------------------------------------------- // STATIC FILES (SERVE UI) // --------------------------------------------------------- app.use(express.static(path.join(__dirname, "../public"))); // Serve index.html at root app.get("/", (_req, res) => { res.sendFile(path.join(__dirname, "../public/index.html")); }); // --------------------------------------------------------- // API ROUTES // --------------------------------------------------------- app.use(bodyParser.json()); app.get('/servers', (_req, res) => { res.json(manager.list()); }); app.get('/servers/:id', (req, res) => { const server = manager.get(req.params.id); if (!server) return res.status(404).json({ error: 'Not found' }); res.json(server); }); app.post('/servers', async (req, res) => { try { const { id, name } = req.body; if (!id) return res.status(400).json({ error: 'id required' }); const server = await manager.create(id, name); res.status(201).json(server); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/servers/:id/start', async (req, res) => { try { const server = await manager.start(req.params.id); res.json(server); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/servers/:id/stop', async (req, res) => { try { const server = await manager.stop(req.params.id); res.json(server); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/servers/:id/restart', async (req, res) => { try { const server = await manager.restart(req.params.id); res.json(server); } catch (err) { res.status(500).json({ error: err.message }); } }); // --------------------------------------------------------- // CONFIG READ // --------------------------------------------------------- app.get('/servers/:id/config', (req, res) => { try { const cfg = manager.readConfig(req.params.id); res.json(cfg); } catch (err) { res.status(404).json({ error: err.message }); } }); // --------------------------------------------------------- // CONFIG UPDATE // --------------------------------------------------------- app.put('/servers/:id/config', async (req, res) => { try { const { serverCfg, worldCfg } = req.body; const server = await manager.updateConfig(req.params.id, serverCfg, worldCfg); res.json(server); } catch (err) { res.status(500).json({ error: err.message }); } }); // --------------------------------------------------------- // AUTO-RESTART TOGGLE // --------------------------------------------------------- app.post('/servers/:id/auto-restart', async (req, res) => { try { const { enabled } = req.body; if (typeof enabled !== 'boolean') { return res.status(400).json({ error: "Field 'enabled' must be boolean" }); } const updated = await manager.setAutoRestart(req.params.id, enabled); res.json(updated); } catch (err) { res.status(500).json({ error: err.message }); } }); // --------------------------------------------------------- // CRASH EVENT LOG RETRIEVAL // --------------------------------------------------------- app.get('/servers/:id/events', async (req, res) => { try { const events = await manager.getEvents(req.params.id); res.json(events); } catch (err) { res.status(500).json({ error: err.message }); } }); // --------------------------------------------------------- // LIVE LOG STREAMING (SSE) // --------------------------------------------------------- app.get('/servers/:id/logs/stream', (req, res) => { const id = req.params.id; const type = req.query.type === 'stderr' ? 'stderr' : 'stdout'; const filePath = `${logsDir(id)}/${type}.log`; res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // Send existing log content if (existsSync(filePath)) { const existing = readFileSync(filePath, 'utf-8') .split('\n') .filter(Boolean); for (const line of existing) { res.write(`data: ${line}\n\n`); } } // Watch for new log lines const watcher = fs.watch(filePath, () => { const content = readFileSync(filePath, 'utf-8') .trim() .split('\n') .slice(-1)[0]; res.write(`data: ${content}\n\n`); }); req.on('close', () => watcher.close()); }); app.delete('/servers/:id', async (req, res) => { try { await manager.delete(req.params.id); res.status(204).end(); } catch (err) { res.status(500).json({ error: err.message }); } }); return app; }