import type { APIRoute } from "astro"; import { renderToStream } from "@ceereals/vue-pdf"; import { db } from "../../../../db"; import { invoices, invoiceItems, clients, organizations, members, } from "../../../../db/schema"; import { eq, and } from "drizzle-orm"; import { createInvoiceDocument } from "../../../../pdf/generateInvoicePDF"; export const GET: APIRoute = async ({ params, locals }) => { const user = locals.user; if (!user) { return new Response("Unauthorized", { status: 401 }); } const { id } = params; if (!id) { return new Response("Invoice ID is required", { status: 400 }); } // Fetch invoice with related data const invoiceResult = await db .select({ invoice: invoices, client: clients, organization: organizations, }) .from(invoices) .leftJoin(clients, eq(invoices.clientId, clients.id)) .innerJoin(organizations, eq(invoices.organizationId, organizations.id)) .where(eq(invoices.id, id)) .get(); if (!invoiceResult) { return new Response("Invoice not found", { status: 404 }); } const { invoice, client, organization } = invoiceResult; // Verify membership const membership = await db .select() .from(members) .where( and( eq(members.userId, user.id), eq(members.organizationId, invoice.organizationId), ), ) .get(); if (!membership) { return new Response("Not authorized", { status: 403 }); } // Fetch items const items = await db .select() .from(invoiceItems) .where(eq(invoiceItems.invoiceId, invoice.id)) .all(); try { const document = createInvoiceDocument({ invoice: { ...invoice, notes: invoice.notes || null, discountValue: invoice.discountValue ?? null, discountType: invoice.discountType ?? null, discountAmount: invoice.discountAmount ?? null, taxRate: invoice.taxRate ?? null, }, items, client: { name: client?.name || "Deleted Client", email: client?.email || null, street: client?.street || null, city: client?.city || null, state: client?.state || null, zip: client?.zip || null, country: client?.country || null, }, organization: { name: organization.name, street: organization.street || null, city: organization.city || null, state: organization.state || null, zip: organization.zip || null, country: organization.country || null, logoUrl: organization.logoUrl || null, }, }); const stream = await renderToStream(document); const chunks: Uint8Array[] = []; for await (const chunk of stream) { chunks.push(chunk as Uint8Array); } const buffer = Buffer.concat(chunks); return new Response(buffer, { headers: { "Content-Type": "application/pdf", "Content-Disposition": `attachment; filename="${invoice.number.replace(/[^a-zA-Z0-9_\-\.]/g, "_")}.pdf"`, }, }); } catch (error) { console.error("Error generating PDF:", error); return new Response("Failed to generate PDF", { status: 500 }); } };