initial commit

This commit is contained in:
syntaxbullet
2026-05-13 17:26:13 +02:00
commit 99569d2cf3
43 changed files with 8725 additions and 0 deletions

224
index.ts Normal file
View File

@@ -0,0 +1,224 @@
import { routes as templatebuilder, workerPath } from "./packages/templatebuilder/index";
import { routes as extractor } from "./packages/extractor/index";
import { routes as summarizer } from "./packages/summarizer/index";
import { join } from "node:path";
import { dataPath } from "./deployPaths";
import {
addSecurityHeaders,
isSafeStem,
login,
loginPage,
logout,
requireAuth,
securityHeaders,
withAuth,
} from "./security";
const SUMMARIZER_OUTPUTS_DIR =
process.env.BMP_SUMMARIZER_OUTPUTS_DIR ??
dataPath(join(import.meta.dir, "packages/summarizer/outputs"), "summarizer", "outputs");
const ROOT_PAGE_PATH = join(import.meta.dir, "index.html");
const BMP_LOGO_PATH = join(import.meta.dir, "assets/bmp-logo.png");
const PORT = Number(process.env.PORT ?? 3000);
function contentTypeFor(path: string): string {
if (path.endsWith(".json")) return "application/json; charset=utf-8";
if (path.endsWith(".xlsx")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
if (path.endsWith(".pdf")) return "application/pdf";
if (path.endsWith(".html")) return "text/html; charset=utf-8";
return "application/octet-stream";
}
async function serveSummaryDownload(req: Request, stem: string, file: string): Promise<Response> {
const blocked = await requireAuth(req);
if (blocked) return blocked;
if (
!isSafeStem(stem) ||
file !== `${stem}.json` &&
file !== `${stem}.xlsx` &&
file !== `${stem}.pdf` &&
file !== `${stem}.report.html` &&
file !== `${stem}.questions.pdf` &&
file !== `${stem}.questions.html`
) {
return new Response("Not found", { status: 404 });
}
const path = join(SUMMARIZER_OUTPUTS_DIR, stem, file);
const output = Bun.file(path);
if (!(await output.exists())) {
return new Response("Not found", { status: 404 });
}
return new Response(output, {
headers: {
...securityHeaders,
"Content-Type": contentTypeFor(file),
"Cache-Control": "private, no-store",
"Content-Disposition": `attachment; filename="${file.replaceAll('"', "")}"`,
},
});
}
Bun.serve({
port: PORT,
routes: {
"/login": {
GET: loginPage,
POST: login,
},
"/healthz": {
GET: () => Response.json({ ok: true }),
},
"/logout": {
POST: withAuth(async () => logout(), { csrf: true }),
},
"/": {
GET: withAuth(async () => new Response(Bun.file(ROOT_PAGE_PATH), {
headers: { "Content-Type": "text/html; charset=utf-8" },
})),
},
"/assets/bmp-logo.png": {
GET: withAuth(async () => new Response(Bun.file(BMP_LOGO_PATH), {
headers: { "Content-Type": "image/png", "Cache-Control": "private, max-age=86400" },
})),
},
"/api/pipeline/result/:stem": {
GET: withAuth(async (req: Request) => {
const stem = (req as Request & { params: Record<string, string | undefined> }).params.stem ?? "";
if (!isSafeStem(stem)) {
return Response.json({ error: "Invalid result id" }, { status: 400 });
}
const jsonPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.json`);
const xlsxPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.xlsx`);
const pdfPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.pdf`);
const reportHtmlPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.report.html`);
const questionsPdfPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.questions.pdf`);
const questionsHtmlPath = join(SUMMARIZER_OUTPUTS_DIR, stem, `${stem}.questions.html`);
const jsonFile = Bun.file(jsonPath);
if (!(await jsonFile.exists())) {
return Response.json({ error: "Summary result not found" }, { status: 404 });
}
const downloads = [
{
label: "JSON herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.json`)}`,
},
];
if (await Bun.file(xlsxPath).exists()) {
downloads.push({
label: "XLSX herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.xlsx`)}`,
});
}
if (await Bun.file(pdfPath).exists()) {
downloads.push({
label: "PDF herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.pdf`)}`,
});
}
if (await Bun.file(reportHtmlPath).exists()) {
downloads.push({
label: "Report-HTML herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.report.html`)}`,
});
}
if (await Bun.file(questionsPdfPath).exists()) {
downloads.push({
label: "Fragen-PDF herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.questions.pdf`)}`,
});
}
if (await Bun.file(questionsHtmlPath).exists()) {
downloads.push({
label: "Fragen-HTML herunterladen",
url: `/downloads/summarizer/${encodeURIComponent(stem)}/${encodeURIComponent(`${stem}.questions.html`)}`,
});
}
return Response.json({
ok: true,
stem,
summary: await jsonFile.json(),
downloads,
});
}),
},
"/downloads/summarizer/:stem/:file": {
GET: async (req: Request) => {
const params = (req as Request & { params: Record<string, string | undefined> }).params;
const stem = params.stem ?? "";
const file = params.file ?? "";
return serveSummaryDownload(req, stem, file);
},
},
...templatebuilder,
...extractor,
...summarizer,
},
async fetch(req) {
const { pathname } = new URL(req.url);
if (pathname === "/pdf.worker.mjs") {
const blocked = await requireAuth(req);
if (blocked) return blocked;
return addSecurityHeaders(new Response(Bun.file(workerPath), {
headers: { "Content-Type": "application/javascript", "Cache-Control": "private, max-age=3600" },
}));
}
if (pathname === "/login" && req.method === "POST") {
return login(req);
}
if (pathname === "/logout" && req.method === "POST") {
const blocked = await requireAuth(req, { csrf: true });
if (blocked) return blocked;
return logout();
}
if (pathname === "/login" && req.method === "GET") {
return loginPage(req);
}
if (pathname === "/healthz" && req.method === "GET") {
return Response.json({ ok: true });
}
if (pathname === "/") {
const blocked = await requireAuth(req);
if (blocked) return blocked;
return addSecurityHeaders(new Response(Bun.file(ROOT_PAGE_PATH), {
headers: { "Content-Type": "text/html; charset=utf-8" },
}));
}
if (pathname === "/assets/bmp-logo.png") {
const blocked = await requireAuth(req);
if (blocked) return blocked;
return addSecurityHeaders(new Response(Bun.file(BMP_LOGO_PATH), {
headers: { "Content-Type": "image/png", "Cache-Control": "private, max-age=86400" },
}));
}
if (pathname.startsWith("/_bun/")) {
const blocked = await requireAuth(req);
if (blocked) return blocked;
return new Response("Not found", {
status: 404,
headers: securityHeaders,
});
}
if (pathname.startsWith("/downloads/summarizer/")) {
const [stem = "", file = ""] = pathname
.slice("/downloads/summarizer/".length)
.split("/")
.map((part) => decodeURIComponent(part));
return serveSummaryDownload(req, stem, file);
}
return new Response("Not found", { status: 404, headers: securityHeaders });
},
development: { hmr: true, console: true },
});
console.log("bmp-rewrite");
console.log(` pipeline → http://localhost:${PORT}/`);
console.log(` templatebuilder → http://localhost:${PORT}/builder`);
console.log(` extractor → http://localhost:${PORT}/extractor`);
console.log(` summarizer → http://localhost:${PORT}/summarizer`);