Added node based web map renderer
This commit is contained in:
parent
82ae89865a
commit
24a4d90a3e
19 changed files with 3970 additions and 0 deletions
102
map_renderer/src/server.js
Normal file
102
map_renderer/src/server.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
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}`);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue