feat: expose boss via nginx base path
This commit is contained in:
@@ -5,6 +5,22 @@ import { BossEngine } from "./engine.js";
|
||||
|
||||
const engine = new BossEngine();
|
||||
const app = Fastify({ logger: process.env.BOSS_DEBUG === "1" });
|
||||
const basePath = normalizeBasePath(process.env.BOSS_BASE_PATH ?? "");
|
||||
|
||||
function normalizeBasePath(input: string) {
|
||||
if (!input || input === "/") {
|
||||
return "";
|
||||
}
|
||||
const normalized = input.startsWith("/") ? input : `/${input}`;
|
||||
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
||||
}
|
||||
|
||||
function withBase(pathname: string) {
|
||||
if (!basePath) {
|
||||
return pathname;
|
||||
}
|
||||
return pathname === "/" ? `${basePath}/` : `${basePath}${pathname}`;
|
||||
}
|
||||
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
const message =
|
||||
@@ -38,61 +54,61 @@ app.setErrorHandler((error, request, reply) => {
|
||||
|
||||
await app.register(fastifyStatic, {
|
||||
root: path.resolve(process.cwd(), "public"),
|
||||
prefix: "/",
|
||||
prefix: withBase("/"),
|
||||
});
|
||||
|
||||
function ok() {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
app.get("/api/health", async () => ({
|
||||
app.get(withBase("/api/health"), async () => ({
|
||||
status: "ok",
|
||||
sessions: engine.getState().sessions.length,
|
||||
workers: engine.getState().workers.length,
|
||||
}));
|
||||
|
||||
app.get("/api/bootstrap", async () => engine.bootstrap());
|
||||
app.get(withBase("/api/bootstrap"), async () => engine.bootstrap());
|
||||
|
||||
app.get("/api/events", async (request) => {
|
||||
app.get(withBase("/api/events"), async (request) => {
|
||||
const query = request.query as { limit?: string };
|
||||
const limit = Number(query.limit ?? 100);
|
||||
return engine.listEvents(Number.isFinite(limit) ? limit : 100);
|
||||
});
|
||||
|
||||
app.get("/api/sessions", async () => engine.listSessions());
|
||||
app.get(withBase("/api/sessions"), async () => engine.listSessions());
|
||||
|
||||
app.post("/api/sessions", async (request) => {
|
||||
app.post(withBase("/api/sessions"), async (request) => {
|
||||
const body = (request.body ?? {}) as { title?: string };
|
||||
return engine.createSession(body.title);
|
||||
});
|
||||
|
||||
app.post("/api/sessions/:sessionId/archive", async (request) => {
|
||||
app.post(withBase("/api/sessions/:sessionId/archive"), async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
return engine.archiveSession(params.sessionId);
|
||||
});
|
||||
|
||||
app.post("/api/sessions/:sessionId/restore", async (request) => {
|
||||
app.post(withBase("/api/sessions/:sessionId/restore"), async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
return engine.restoreSession(params.sessionId);
|
||||
});
|
||||
|
||||
app.get("/api/sessions/:sessionId", async (request) => {
|
||||
app.get(withBase("/api/sessions/:sessionId"), async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
return engine.getSession(params.sessionId);
|
||||
});
|
||||
|
||||
app.get("/api/tasks/:taskId", async (request) => {
|
||||
app.get(withBase("/api/tasks/:taskId"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.getTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/sessions/:sessionId/messages", async (request) => {
|
||||
app.post(withBase("/api/sessions/:sessionId/messages"), async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
const body = (request.body ?? {}) as { content?: string; channel?: string };
|
||||
return engine.addMessage(params.sessionId, body.content ?? "", body.channel ?? "web");
|
||||
});
|
||||
|
||||
app.get("/api/events/stream", async (_request, reply) => {
|
||||
app.get(withBase("/api/events/stream"), async (_request, reply) => {
|
||||
reply.raw.setHeader("Content-Type", "text/event-stream");
|
||||
reply.raw.setHeader("Cache-Control", "no-cache");
|
||||
reply.raw.setHeader("Connection", "keep-alive");
|
||||
@@ -113,14 +129,14 @@ app.get("/api/events/stream", async (_request, reply) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/workers", async () => engine.getState().workers);
|
||||
app.get(withBase("/api/workers"), async () => engine.getState().workers);
|
||||
|
||||
app.get("/api/workers/:workerId", async (request) => {
|
||||
app.get(withBase("/api/workers/:workerId"), async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return engine.getWorker(params.workerId);
|
||||
});
|
||||
|
||||
app.post("/api/workers/register", async (request) => {
|
||||
app.post(withBase("/api/workers/register"), async (request) => {
|
||||
const body = request.body as {
|
||||
name?: string;
|
||||
os?: "windows" | "macos" | "linux";
|
||||
@@ -133,25 +149,25 @@ app.post("/api/workers/register", async (request) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/workers/:workerId/heartbeat", async (request) => {
|
||||
app.post(withBase("/api/workers/:workerId/heartbeat"), async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
const body = (request.body ?? {}) as { load?: number };
|
||||
return engine.heartbeat(params.workerId, body.load ?? 0);
|
||||
});
|
||||
|
||||
app.post("/api/workers/:workerId/offline", async (request) => {
|
||||
app.post(withBase("/api/workers/:workerId/offline"), async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return engine.markWorkerOffline(params.workerId);
|
||||
});
|
||||
|
||||
app.post("/api/workers/:workerId/claim-next", async (request) => {
|
||||
app.post(withBase("/api/workers/:workerId/claim-next"), async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return {
|
||||
task: engine.claimNextTask(params.workerId),
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/progress", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/progress"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
const body = request.body as {
|
||||
workerId: string;
|
||||
@@ -168,7 +184,7 @@ app.post("/api/tasks/:taskId/progress", async (request) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/complete", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/complete"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
const body = request.body as {
|
||||
workerId: string;
|
||||
@@ -177,7 +193,7 @@ app.post("/api/tasks/:taskId/complete", async (request) => {
|
||||
return engine.completeTask(params.taskId, body.workerId, body.summary);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/fail", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/fail"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
const body = request.body as {
|
||||
workerId: string;
|
||||
@@ -186,27 +202,27 @@ app.post("/api/tasks/:taskId/fail", async (request) => {
|
||||
return engine.failTask(params.taskId, body.workerId, body.errorMessage);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/pause", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/pause"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.pauseTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/cancel", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/cancel"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.cancelTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/resume", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/resume"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.resumeTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/requeue", async (request) => {
|
||||
app.post(withBase("/api/tasks/:taskId/requeue"), async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.requeueTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/approvals/:approvalId/respond", async (request) => {
|
||||
app.post(withBase("/api/approvals/:approvalId/respond"), async (request) => {
|
||||
const params = request.params as { approvalId: string };
|
||||
const body = request.body as {
|
||||
approved: boolean;
|
||||
@@ -215,12 +231,12 @@ app.post("/api/approvals/:approvalId/respond", async (request) => {
|
||||
return engine.respondApproval(params.approvalId, body.approved, body.responder ?? "user");
|
||||
});
|
||||
|
||||
app.post("/api/demo/reset", async (request) => {
|
||||
app.post(withBase("/api/demo/reset"), async (request) => {
|
||||
const body = (request.body ?? {}) as { preserveWorkers?: boolean };
|
||||
return engine.resetDemo(body.preserveWorkers ?? true);
|
||||
});
|
||||
|
||||
app.post("/api/reconcile", async () => engine.reconcileNow());
|
||||
app.post(withBase("/api/reconcile"), async () => engine.reconcileNow());
|
||||
|
||||
const port = Number(process.env.PORT ?? 43210);
|
||||
await app.listen({ port, host: "0.0.0.0" });
|
||||
|
||||
Reference in New Issue
Block a user