Crusader_Decomp/map_renderer/src/server.js
2026-03-27 10:04:44 +01:00

102 lines
No EOL
3.8 KiB
JavaScript

import express from "express";
import path from "node:path";
import { PORT, PUBLIC_ROOT } from "./config.js";
import { BuildManager } from "./lib/build-manager.js";
import { detectCatalog, getGameConfig } from "./lib/catalog.js";
const app = express();
const catalog = detectCatalog();
const builds = new BuildManager(catalog);
app.disable("x-powered-by");
app.use(express.json({ limit: "64kb" }));
app.use(express.static(PUBLIC_ROOT, { extensions: ["html"] }));
app.get("/api/maps", (_request, response) => {
response.json(builds.listCatalog());
});
app.post("/api/builds", async (request, response) => {
try {
const game = String(request.body?.game ?? "");
const mapId = Number.parseInt(String(request.body?.mapId ?? ""), 10);
const options = {
includeEditor: request.body?.includeEditor !== false,
includeRoofs: request.body?.includeRoofs === true
};
const gameConfig = getGameConfig(game);
if (!gameConfig) {
response.status(400).json({ error: "Unknown game id" });
return;
}
if (!Number.isInteger(mapId) || mapId < 0) {
response.status(400).json({ error: "Invalid map id" });
return;
}
const job = await builds.createOrReuseBuild(gameConfig, mapId, options);
response.status(202).json(builds.getPublicJob(job));
} catch (error) {
response.status(500).json({ error: error instanceof Error ? error.message : String(error) });
}
});
app.get("/api/builds/:id", (request, response) => {
const job = builds.getJob(request.params.id);
if (!job) {
response.status(404).json({ error: "Unknown build id" });
return;
}
response.json(builds.getPublicJob(job));
});
app.get("/api/maps/:game/:mapId/metadata", (request, response) => {
try {
const buildId = String(request.query.buildId ?? "");
const mapId = Number.parseInt(request.params.mapId, 10);
const metadata = builds.getMetadata(buildId, request.params.game, mapId);
response.json(metadata);
} catch (error) {
response.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
app.get("/api/maps/:game/:mapId/tiles/:tileX/:tileY.png", (request, response) => {
try {
const buildId = String(request.query.buildId ?? "");
const mapId = Number.parseInt(request.params.mapId, 10);
const tileX = Number.parseInt(request.params.tileX, 10);
const tileY = Number.parseInt(request.params.tileY, 10);
if (!Number.isInteger(tileX) || !Number.isInteger(tileY) || tileX < 0 || tileY < 0) {
response.status(400).json({ error: "Invalid tile coordinates" });
return;
}
const png = builds.renderTile(buildId, request.params.game, mapId, tileX, tileY);
response.setHeader("Content-Type", "image/png");
response.setHeader("Cache-Control", "public, max-age=31536000, immutable");
response.end(png);
} catch (error) {
response.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
app.get("/api/maps/:game/:mapId/download.png", async (request, response) => {
try {
const buildId = String(request.query.buildId ?? "");
const mapId = Number.parseInt(request.params.mapId, 10);
const filePath = await builds.renderFullMap(buildId, request.params.game, mapId);
response.setHeader("Content-Type", "image/png");
response.setHeader("Content-Disposition", `attachment; filename="${path.basename(filePath)}"`);
response.sendFile(path.resolve(filePath), { dotfiles: "allow" });
} catch (error) {
response.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
app.get("/api/health", (_request, response) => {
response.json({ ok: true, games: catalog.games.length });
});
app.listen(PORT, () => {
console.log(`Crusader map renderer listening on http://localhost:${PORT}`);
});