Switch to tags
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled

This commit is contained in:
2026-01-20 11:09:09 -07:00
parent de5b1063b7
commit ad7dc18780
22 changed files with 1516 additions and 1057 deletions

View File

@@ -1,102 +1,100 @@
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 { 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 { id } = params;
const user = locals.user;
const document = createInvoiceDocument({
invoice: {
...invoice,
notes: invoice.notes || null,
// Ensure null safety for optional fields that might be undefined in some runtimes depending on driver
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,
}
});
if (!user || !id) {
return new Response("Unauthorized", { status: 401 });
}
const stream = await renderToStream(document);
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;
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 });
}
const items = await db
.select()
.from(invoiceItems)
.where(eq(invoiceItems.invoiceId, invoice.id))
.all();
if (!client) {
return new Response("Client not found", { status: 404 });
}
const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
console.log = () => {};
console.warn = () => {};
try {
const pdfDocument = createInvoiceDocument({
invoice,
items,
client,
organization,
});
const stream = await renderToStream(pdfDocument);
console.log = originalConsoleLog;
console.warn = originalConsoleWarn;
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;
console.warn = originalConsoleWarn;
throw pdfError;
}
return new Response(stream, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="${invoice.number}.pdf"`,
},
});
} catch (error) {
console.error("Error generating PDF:", error);
return new Response("Error generating PDF", { status: 500 });
return new Response("Failed to generate PDF", { status: 500 });
}
};