44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getSessionUser } from "@/lib/session";
|
|
import { getInvoiceByNumberForOrg, getInvoicePdf } from "@/lib/db";
|
|
|
|
/**
|
|
* GET /api/billing/invoices/[invoiceNumber]/pdf
|
|
*
|
|
* Customer-facing PDF download. Same Uint8Array.from() variance
|
|
* fix as the admin route — see /api/admin/billing/invoices/[id]/pdf
|
|
* for the rationale.
|
|
*
|
|
* Authorization: looks up the invoice by number with org scope
|
|
* baked into the query, then re-fetches the PDF blob by id. A
|
|
* customer can't probe another org's invoice numbers — they get
|
|
* 404 either way.
|
|
*/
|
|
export async function GET(
|
|
_request: Request,
|
|
{ params }: { params: Promise<{ invoiceNumber: string }> }
|
|
) {
|
|
const user = await getSessionUser();
|
|
if (!user) {
|
|
return new NextResponse("Unauthorized", { status: 401 });
|
|
}
|
|
const { invoiceNumber } = await params;
|
|
const detail = await getInvoiceByNumberForOrg(invoiceNumber, user.orgId);
|
|
if (!detail) {
|
|
return new NextResponse("Not found", { status: 404 });
|
|
}
|
|
const pdf = await getInvoicePdf(detail.invoice.id);
|
|
if (!pdf) {
|
|
return new NextResponse("PDF not available", { status: 404 });
|
|
}
|
|
const body = Uint8Array.from(pdf.data);
|
|
return new NextResponse(body, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/pdf",
|
|
"Content-Disposition": `inline; filename="${pdf.filename}"`,
|
|
"Cache-Control": "private, max-age=0, must-revalidate",
|
|
},
|
|
});
|
|
}
|