Release Healer Man 0.1.46
This commit is contained in:
1 parent
f82daf1c13
commit
9ff574ac0b
73 files changed
+33486
-174
No files matched your search
+106
-1
@@ -8,6 +8,7 @@ import { publicManastormAdminConfig } from "./manastorm-config.mjs";
|
||||
import { createOnlineSessionManager } from "./online-session.mjs";
|
||||
import { upgradeArenaWebSocket } from "./websocket.mjs";
|
||||
import { createBundledContentHandler } from "./bundled-content.mjs";
|
||||
import { createBalanceLabJobService } from "./balance-lab-jobs.mjs";
|
||||
|
||||
const MIME_TYPES = new Map([
|
||||
[".css", "text/css; charset=utf-8"], [".glb", "model/gltf-binary"],
|
||||
@@ -178,6 +179,11 @@ export function createGameServer(options = {}) {
|
||||
const trustProxy = String(options.trustProxy ?? process.env.TRUST_PROXY ?? "").toLowerCase() === "true";
|
||||
const enableShelvedModes = options.enableShelvedModes ?? booleanFlag(process.env.HEALER_MAN_ENABLE_SHELVED_MODES);
|
||||
const gameDatabase = options.database ?? openGameDatabase(options);
|
||||
const balanceLabJobs = options.balanceLabJobs ?? createBalanceLabJobService({
|
||||
dataDir: options.dataDir ?? path.dirname(gameDatabase.databasePath),
|
||||
projectRoot: options.projectRoot,
|
||||
pythonExecutable: options.pythonExecutable,
|
||||
});
|
||||
const onlineSessions = createOnlineSessionManager();
|
||||
const arenaService = createArenaService({ database: gameDatabase, autoPump: enableShelvedModes });
|
||||
const allowAuthRequest = createAuthLimiter();
|
||||
@@ -211,6 +217,60 @@ export function createGameServer(options = {}) {
|
||||
json(response, 200, { ok: true, service: "healer-man", time: new Date().toISOString() });
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/dungeon-route-approvals" && request.method === "GET") {
|
||||
json(response, 200, gameDatabase.listDungeonRouteApprovals());
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/gm/dungeon-routes" && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
json(response, 200, gameDatabase.listDungeonRouteReviews(account));
|
||||
return;
|
||||
}
|
||||
const dungeonRouteReviewMatch = /^\/api\/gm\/dungeon-routes\/([^/]+)\/([^/]+)\/review$/.exec(url.pathname);
|
||||
if (dungeonRouteReviewMatch && request.method === "PUT") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
const body = await readJson(request, 64 * 1024);
|
||||
const review = gameDatabase.putDungeonRouteReview(
|
||||
account,
|
||||
decodeURIComponent(dungeonRouteReviewMatch[1]),
|
||||
decodeURIComponent(dungeonRouteReviewMatch[2]),
|
||||
body,
|
||||
);
|
||||
json(response, 200, { review });
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/gm/dungeon-sim/replays" && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
json(response, 200, gameDatabase.listDungeonSimulationReplays(account, {
|
||||
dungeonId: url.searchParams.get("dungeon"),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/gm/dungeon-sim/replays" && request.method === "POST") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
const replay = gameDatabase.saveDungeonSimulationReplay(account, await readJson(request, 64 * 1024 * 1024));
|
||||
json(response, 201, { replay });
|
||||
return;
|
||||
}
|
||||
const dungeonSimulationReportMatch = /^\/api\/gm\/dungeon-sim\/replays\/([^/]+)\/report$/.exec(url.pathname);
|
||||
if (dungeonSimulationReportMatch && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
json(response, 200, gameDatabase.getDungeonSimulationReport(account, dungeonSimulationReportMatch[1]), {
|
||||
"Content-Disposition": `attachment; filename="dungeon-simulation-${dungeonSimulationReportMatch[1]}.json"`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const dungeonSimulationReplayMatch = /^\/api\/gm\/dungeon-sim\/replays\/([^/]+)$/.exec(url.pathname);
|
||||
if (dungeonSimulationReplayMatch && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
json(response, 200, gameDatabase.getDungeonSimulationReplay(account, dungeonSimulationReplayMatch[1]));
|
||||
return;
|
||||
}
|
||||
if (dungeonSimulationReplayMatch && request.method === "DELETE") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
json(response, 200, gameDatabase.deleteDungeonSimulationReplay(account, dungeonSimulationReplayMatch[1]));
|
||||
return;
|
||||
}
|
||||
if (!enableShelvedModes && (
|
||||
url.pathname === "/api/manastorm-config"
|
||||
|| url.pathname.startsWith("/api/gm/manastorm-config")
|
||||
@@ -438,6 +498,50 @@ export function createGameServer(options = {}) {
|
||||
json(response, 200, result);
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/gm/balance-lab/jobs" && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
if (!gameDatabase.isGameMaster(account)) {
|
||||
throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
|
||||
}
|
||||
json(response, 200, { jobs: balanceLabJobs.list() });
|
||||
return;
|
||||
}
|
||||
if (url.pathname === "/api/gm/balance-lab/jobs" && request.method === "POST") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
if (!gameDatabase.isGameMaster(account)) {
|
||||
throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
|
||||
}
|
||||
const job = balanceLabJobs.start(await readJson(request, 32 * 1024), account);
|
||||
console.info(`Balance Lab job ${job.id} queued by ${account.username}`);
|
||||
json(response, 202, { job });
|
||||
return;
|
||||
}
|
||||
const balanceLabArtifactMatch = /^\/api\/gm\/balance-lab\/jobs\/([0-9a-f-]{36})\/artifacts\/([A-Za-z0-9._-]+)$/.exec(url.pathname);
|
||||
if (balanceLabArtifactMatch && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
if (!gameDatabase.isGameMaster(account)) {
|
||||
throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
|
||||
}
|
||||
const artifact = balanceLabJobs.artifact(balanceLabArtifactMatch[1], balanceLabArtifactMatch[2]);
|
||||
response.writeHead(200, {
|
||||
"Content-Type": artifact.contentType,
|
||||
"Content-Length": artifact.size,
|
||||
"Cache-Control": "no-store",
|
||||
"Content-Disposition": `inline; filename="${balanceLabArtifactMatch[2]}"`,
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
});
|
||||
artifact.stream().pipe(response);
|
||||
return;
|
||||
}
|
||||
const balanceLabJobMatch = /^\/api\/gm\/balance-lab\/jobs\/([0-9a-f-]{36})$/.exec(url.pathname);
|
||||
if (balanceLabJobMatch && request.method === "GET") {
|
||||
const account = gameDatabase.authenticate(bearerToken(request));
|
||||
if (!gameDatabase.isGameMaster(account)) {
|
||||
throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
|
||||
}
|
||||
json(response, 200, { job: balanceLabJobs.get(balanceLabJobMatch[1]) });
|
||||
return;
|
||||
}
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
json(response, 404, { error: "API route not found.", code: "not_found" });
|
||||
return;
|
||||
@@ -491,11 +595,12 @@ export function createGameServer(options = {}) {
|
||||
});
|
||||
|
||||
server.on("close", () => {
|
||||
balanceLabJobs.close?.();
|
||||
onlineSessions.clear();
|
||||
arenaService.close();
|
||||
gameDatabase.close();
|
||||
});
|
||||
return { server, database: gameDatabase, arenaService, staticDir, contentDir, enableShelvedModes };
|
||||
return { server, database: gameDatabase, arenaService, balanceLabJobs, staticDir, contentDir, enableShelvedModes };
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
|
||||
Reference in new issue
Block a user