fix(po-pdf): capture PB token on upload and route GET through proxy

This commit is contained in:
SJCoetz 2026-07-09 22:18:53 +02:00
parent 1e99d23e8b
commit 6a7dcc0af2

View file

@ -212,29 +212,32 @@ export async function POST(
// PB file fields accept an array of File/Blob. Append to the
// existing array so every regen is preserved (operator can scroll
// through documents_history to find v1, v2, v3...).
const newHistoryEntry: PurchaseOrderDocumentEntry = {
version: nextVersion,
file: filename,
// Task 2 will swap this for the real PB token returned from
// the upload response. For now we persist an empty string so
// the schema compiles; the GET handler falls back to
// `documents[idx]` when token is empty (legacy rows).
token: "",
generatedAt: generatedAtIso,
generatedBy: body.generatedBy || "",
reason: (body.notes || "").slice(0, 500),
status: "current",
};
const formData = new FormData();
formData.append(
"documents",
new Blob([new Uint8Array(pdfBuffer)], { type: "application/pdf" }),
filename
);
// PB SDK FormData update only accepts the file — append the history
// via a second update call (it's JSON, not a file).
await pb.collection("purchase_orders").update(poId, formData);
// PB returns the stored file tokens (slugged) in the response.
// We need the actual on-disk token for later fetches, not the
// human-readable `filename` we sent. Append to the existing array
// and pick the last element.
const updated = await pb.collection("purchase_orders").update<{
documents: string[] | string;
}>(poId, formData);
const docsRaw = updated?.documents ?? [];
const docs = Array.isArray(docsRaw) ? docsRaw : [docsRaw];
const newToken = docs[docs.length - 1] ?? "";
const newHistoryEntry: PurchaseOrderDocumentEntry = {
version: nextVersion,
file: filename,
token: newToken,
generatedAt: generatedAtIso,
generatedBy: body.generatedBy || "",
reason: (body.notes || "").slice(0, 500),
status: "current",
};
// Append the new history entry. PB handles the array merge via
// JSON Patch — actually no, with plain PATCH we'd replace the
@ -316,7 +319,9 @@ export async function GET(
const v = parseInt(versionParam, 10);
entry = history.find((h) => h.version === v);
} else {
entry = history.find((h) => h.status === "current") ?? history[history.length - 1];
entry =
history.find((h) => h.status === "current") ??
history[history.length - 1];
}
if (!entry) {
@ -326,21 +331,48 @@ export async function GET(
);
}
const fileUrl = pb.files.getURL(
{ collectionId: "purchase_orders", id: poId },
entry.file,
);
const fileRes = await fetch(fileUrl, {
headers: { Authorization: pb.authStore.token ?? "" },
});
if (!fileRes.ok) {
// Resolve a token. Newer rows have `entry.token` (PB-stored slug).
// Legacy rows may have only `entry.file` (the human-readable name).
// For legacy rows, fall back to the live `documents` array — the
// POST handler always appends to both arrays in sync, so index
// alignment gives us the right token.
let token = entry.token;
if (!token) {
const docsRaw = po.documents ?? [];
const docs = Array.isArray(docsRaw) ? docsRaw : [docsRaw];
const idx = history.findIndex((h) => h.version === entry!.version);
token = docs[idx] || docs[0] || "";
}
if (!token) {
return NextResponse.json(
{ error: `Upstream PB file fetch failed: ${fileRes.status}` },
{ status: 502 }
{
error: `PDF token missing for version ${entry.version} — please regenerate.`,
},
{ status: 404 }
);
}
const buf = Buffer.from(await fileRes.arrayBuffer());
// Hand off to the existing file proxy. The proxy already has
// `purchase_orders` whitelisted and streams the bytes back with
// admin auth.
const proxyUrl = `/api/v1/files/purchase_orders/${poId}/${encodeURIComponent(token)}`;
const proxyRes = await fetch(
new URL(proxyUrl, req.nextUrl.origin).toString(),
{
headers: { cookie: req.headers.get("cookie") ?? "" },
}
);
if (!proxyRes.ok) {
const body = await proxyRes.json().catch(() => ({}));
return NextResponse.json(
{ error: body?.error || `Proxy fetch failed: ${proxyRes.status}` },
{ status: proxyRes.status === 404 ? 404 : 502 }
);
}
const buf = Buffer.from(await proxyRes.arrayBuffer());
const safePo = String(po.po_number || poId).replace(/[^\w.-]/g, "_");
const filename = `PO-v${entry.version}-${safePo}.pdf`;