This commit is contained in:
104
src/pages/api/invoices/[id]/generate.ts
Normal file
104
src/pages/api/invoices/[id]/generate.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { db } from "../../../../db";
|
||||
import {
|
||||
invoices,
|
||||
invoiceItems,
|
||||
clients,
|
||||
organizations,
|
||||
members,
|
||||
} from "../../../../db/schema";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { renderToStream } from "@ceereals/vue-pdf";
|
||||
import { createInvoiceDocument } from "../../../../pdf/generateInvoicePDF";
|
||||
|
||||
export const GET: APIRoute = async ({ params, locals }) => {
|
||||
try {
|
||||
const { id } = params;
|
||||
const user = locals.user;
|
||||
|
||||
if (!user || !id) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
// 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 access
|
||||
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("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
// Fetch items
|
||||
const items = await db
|
||||
.select()
|
||||
.from(invoiceItems)
|
||||
.where(eq(invoiceItems.invoiceId, invoice.id))
|
||||
.all();
|
||||
|
||||
if (!client) {
|
||||
return new Response("Client not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Generate PDF using Vue PDF
|
||||
// Suppress verbose logging from PDF renderer
|
||||
const originalConsoleLog = console.log;
|
||||
console.log = () => {};
|
||||
|
||||
try {
|
||||
const pdfDocument = createInvoiceDocument({
|
||||
invoice,
|
||||
items,
|
||||
client,
|
||||
organization,
|
||||
});
|
||||
|
||||
const stream = await renderToStream(pdfDocument);
|
||||
|
||||
// Restore console.log
|
||||
console.log = originalConsoleLog;
|
||||
|
||||
const filename = `${invoice.type}_${invoice.number.replace(/[^a-zA-Z0-9]/g, "_")}.pdf`;
|
||||
|
||||
return new Response(stream as any, {
|
||||
headers: {
|
||||
"Content-Type": "application/pdf",
|
||||
"Content-Disposition": `attachment; filename="${filename}"`,
|
||||
},
|
||||
});
|
||||
} catch (pdfError) {
|
||||
// Restore console.log on error
|
||||
console.log = originalConsoleLog;
|
||||
throw pdfError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error generating PDF:", error);
|
||||
return new Response("Error generating PDF", { status: 500 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user