Files
mini-windrose-manager/dist/api.js
T

170 lines
6.1 KiB
JavaScript

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;
}