117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
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 });
|
|
}
|
|
};
|