initial commit
This commit is contained in:
224
index.ts
Normal file
224
index.ts
Normal 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`);
|
||||
Reference in New Issue
Block a user