Surface n8n failures + add upload timeout + dedupe MIME/size constants
- Purchase order upload route now returns 502 n8n_rejected when n8n responds with a non-2xx status, instead of silently claiming success. - Add a 10s AbortController timeout on the n8n fetch, returning 504 n8n_timeout on expiry (502 n8n_unreachable on other network errors). - Extract ALLOWED_MIME and MAX_FILE_BYTES from upload/route.ts into src/lib/purchase-order-schema.ts so the upcoming ingest route can share the same validation constants.
This commit is contained in:
parent
508e78f72b
commit
2be0c8de8b
2 changed files with 34 additions and 12 deletions
|
|
@ -1,16 +1,9 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { ALLOWED_MIME, MAX_FILE_BYTES } from "@/lib/purchase-order-schema";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const ALLOWED_MIME = new Set([
|
||||
"application/pdf",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
]);
|
||||
const MAX_FILE_BYTES = 20 * 1024 * 1024;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const start = Date.now();
|
||||
|
||||
|
|
@ -55,14 +48,43 @@ export async function POST(request: Request) {
|
|||
n8nForm.append("size", String(file.size));
|
||||
|
||||
let n8nRes: Response;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10_000);
|
||||
try {
|
||||
n8nRes = await fetch(n8nUrl, { method: "POST", body: n8nForm });
|
||||
n8nRes = await fetch(n8nUrl, {
|
||||
method: "POST",
|
||||
body: n8nForm,
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
const isAbort = err instanceof Error && err.name === "AbortError";
|
||||
console.error("[po-upload] n8n unreachable", {
|
||||
filename: file.name,
|
||||
timed_out: isAbort,
|
||||
err: String(err),
|
||||
});
|
||||
return NextResponse.json({ error: "n8n_unreachable" }, { status: 502 });
|
||||
return NextResponse.json(
|
||||
{ error: isAbort ? "n8n_timeout" : "n8n_unreachable" },
|
||||
{ status: isAbort ? 504 : 502 },
|
||||
);
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!n8nRes.ok) {
|
||||
console.error("[po-upload] n8n rejected", {
|
||||
filename: file.name,
|
||||
n8n_status: n8nRes.status,
|
||||
statusText: n8nRes.statusText,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "n8n_rejected",
|
||||
n8n_status: n8nRes.status,
|
||||
statusText: n8nRes.statusText,
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
|
||||
console.log("[po-upload] forwarded", {
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ export type PurchaseOrderIngestPayload = {
|
|||
raw_ocr: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const ALLOWED_MIME = new Set([
|
||||
export const ALLOWED_MIME = new Set([
|
||||
"application/pdf",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
]);
|
||||
const MAX_FILE_BYTES = 20 * 1024 * 1024;
|
||||
export const MAX_FILE_BYTES = 20 * 1024 * 1024;
|
||||
|
||||
export type ValidationResult =
|
||||
| { ok: true; payload: PurchaseOrderIngestPayload }
|
||||
|
|
|
|||
Loading…
Reference in a new issue