Files
beenvoice/src/app/api/i/[token]/pdf/route.ts
T

96 lines
2.6 KiB
TypeScript

import { NextResponse } from "next/server";
import { eq } from "drizzle-orm";
import { db } from "~/server/db";
import { invoices, platformSettings } from "~/server/db/schema";
import { generateInvoicePDFBlob } from "~/lib/pdf-export";
export const runtime = "nodejs";
export async function GET(
request: Request,
{ params }: { params: Promise<{ token: string }> },
) {
const { token } = await params;
const invoice = await db.query.invoices.findFirst({
where: eq(invoices.publicToken, token),
with: {
client: true,
// Explicit allowlist: token-based public route — never fetch
// secret fields (resendApiKey, resendDomain) for an unauthenticated request.
business: {
columns: {
id: true,
name: true,
nickname: true,
email: true,
phone: true,
addressLine1: true,
addressLine2: true,
city: true,
state: true,
postalCode: true,
country: true,
website: true,
taxId: true,
logoStorageKey: true,
logoMimeType: true,
hideNameWithLogo: true,
},
},
items: {
orderBy: (i, { asc }) => [
asc(i.date),
asc(i.position),
asc(i.createdAt),
],
},
},
});
if (!invoice) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
if (invoice.publicTokenExpiresAt && new Date(invoice.publicTokenExpiresAt) < new Date()) {
return NextResponse.json({ error: "This link has expired" }, { status: 410 });
}
const settings = await db.query.platformSettings.findFirst({
where: eq(platformSettings.id, "global"),
});
const pdfBlob = await generateInvoicePDFBlob(
invoice,
{
pdfTemplate: settings?.pdfTemplate as "classic" | "minimal" | undefined,
pdfAccentColor: settings?.pdfAccentColor,
pdfFontFamily: settings?.pdfFontFamily as
| "sans"
| "serif"
| "mono"
| undefined,
pdfNumericFontFamily: settings?.pdfNumericFontFamily as
| "sans"
| "serif"
| "mono"
| undefined,
pdfFooterText: settings?.pdfFooterText,
pdfShowLogo: settings?.pdfShowLogo,
pdfShowPageNumbers: settings?.pdfShowPageNumbers,
},
{ logoBaseUrl: new URL(request.url).origin },
);
const buffer = await pdfBlob.arrayBuffer();
const filename = `invoice-${invoice.invoiceNumber}.pdf`;
return new Response(buffer, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `inline; filename="${filename}"`,
"Cache-Control": "private, no-store",
},
});
}