import { serverAuth } from "@/lib/pb"; import { revalidateInvoicePages } from "@/lib/invoice-revalidate"; export const dynamic = "force-dynamic"; // POST /api/invoices/upload // Body: multipart/form-data with `file` and `invoiceNo`. // // Stores the original document against the invoice's `source_pdf` file field so the // source of every extracted figure stays attached to the row it came from. // // Accepts PDF only — an invoice in any other format cannot have its line items verified // against the PDF totals, and accepting it silently would break that guarantee. const MAX_BYTES = 20 * 1024 * 1024; // matches the PocketBase field's maxSize export async function POST(req: Request) { let form: FormData; try { form = await req.formData(); } catch { return Response.json({ ok: false, error: "Expected multipart/form-data" }, { status: 400 }); } const file = form.get("file"); const invoiceNo = String(form.get("invoiceNo") ?? "").trim(); if (!(file instanceof File) || file.size === 0) { return Response.json({ ok: false, error: "No file received" }, { status: 400 }); } if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) { return Response.json( { ok: false, error: "Only PDF invoices are accepted" }, { status: 400 }, ); } if (file.size > MAX_BYTES) { return Response.json( { ok: false, error: `File is ${(file.size / 1048576).toFixed(1)} MB — limit is 20 MB` }, { status: 400 }, ); } const pb = await serverAuth(); try { // Resolve the invoice by number; fall back to the only one present. let target = null; if (invoiceNo) { const found = await pb.collection("invoices").getFullList({ filter: `invoice_no = "${invoiceNo.replace(/"/g, "")}"`, perPage: 1, }); target = found[0] ?? null; if (!target) { return Response.json( { ok: false, error: `No invoice found with number "${invoiceNo}"` }, { status: 404 }, ); } } else { const all = await pb.collection("invoices").getFullList({ perPage: 2 }); if (all.length === 0) { return Response.json({ ok: false, error: "No invoices exist yet" }, { status: 404 }); } if (all.length > 1) { return Response.json( { ok: false, error: "More than one invoice — specify which by number" }, { status: 400 }, ); } target = all[0]; } const safeName = file.name.replace(/[^\w.\- ]+/g, "_").slice(-120); const form2 = new FormData(); form2.append("source_pdf", new File([await file.arrayBuffer()], safeName, { type: "application/pdf" })); const updated = await pb.collection("invoices").update(target.id, form2); revalidateInvoicePages(); const stored = updated.source_pdf as string | undefined; return Response.json({ ok: true, invoice_no: target.invoice_no, file: safeName, size: file.size, // PocketBase returns "filename" or "filename_abcd1234"; the base name is the file. path: stored?.split("/").pop() ?? null, }); } catch (e: any) { return Response.json( { ok: false, error: e?.message || "Upload failed" }, { status: 500 }, ); } }