96 lines
2.6 KiB
TypeScript
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",
|
|
},
|
|
});
|
|
}
|