Remove blocking PDF generation from appointment POST (LibreOffice hangs from Next.js)
This commit is contained in:
parent
e8ef4589c9
commit
937fa922cc
2 changed files with 4 additions and 86 deletions
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { fetchCollection, createRecord, updateRecordMultipart } from "@/lib/pb";
|
||||
import { writeFileSync, readFileSync, unlinkSync } from "fs";
|
||||
import { execSync, spawn } from "child_process";
|
||||
import { writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
|
|
@ -60,23 +60,7 @@ export async function POST(req: NextRequest) {
|
|||
sign_token: signToken,
|
||||
});
|
||||
|
||||
// 2. Generate letter HTML
|
||||
const letterHtml = buildLetterHtml({ directorName: fullName, companyName: company_name, date: today, signUrl });
|
||||
const htmlPath = `/tmp/appt-letter-${record.id}.html`;
|
||||
writeFileSync(htmlPath, letterHtml, "utf8");
|
||||
|
||||
// 3. Convert to PDF
|
||||
const pdfPath = await htmlToPdf(htmlPath);
|
||||
const hasPdf = pdfPath && pdfPath.length > 0;
|
||||
|
||||
// 4. Upload PDF to PB record (only if conversion succeeded)
|
||||
if (hasPdf) {
|
||||
const pdfBuf = readFileSync(pdfPath);
|
||||
const pdfFile = new File([pdfBuf], "appointment-letter.pdf", { type: "application/pdf" });
|
||||
await updateRecordMultipart("ysterbaard_director_appointments", record.id, {}, [{ field: "letter", file: pdfFile }]);
|
||||
}
|
||||
|
||||
// 5. Send email to director (with or without PDF)
|
||||
// 2. Send HTML email to director with sign link
|
||||
await sendAppointmentEmail({
|
||||
to: emailTo,
|
||||
directorName: fullName || "Director",
|
||||
|
|
@ -95,73 +79,7 @@ export async function POST(req: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── Letter HTML builder ───────────────────────────────────────────────────
|
||||
|
||||
function buildLetterHtml(opts: {
|
||||
directorName: string;
|
||||
companyName: string;
|
||||
date: string;
|
||||
signUrl: string;
|
||||
}): string {
|
||||
const { directorName, companyName, date, signUrl } = opts;
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="utf-8"><style>
|
||||
body { font-family: Arial, sans-serif; font-size: 12pt; margin: 40px 60px; line-height: 1.6; color: #222; }
|
||||
.right { text-align: right; }
|
||||
h1 { font-size: 14pt; text-align: center; margin-bottom: 4px; font-weight: bold; }
|
||||
hr { border: none; border-top: 1px solid #333; margin: 8px 0 16px; }
|
||||
p { margin: 8px 0; }
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="right">
|
||||
<p><strong>${directorName}</strong></p>
|
||||
<p>${date}</p>
|
||||
</div>
|
||||
<h1>LETTER OF APPOINTMENT AS DIRECTOR</h1>
|
||||
<hr />
|
||||
<p>Dear ${directorName},</p>
|
||||
<p>We are pleased to inform you that you have been appointed as a Director of <strong>${companyName}</strong>.</p>
|
||||
<p>Your appointment is subject to your formal acceptance in terms of section 70 of the Companies Act 71 of 2008.</p>
|
||||
<p><strong>To accept your appointment, please click the button below and draw your signature:</strong></p>
|
||||
<p style="text-align:center; margin: 24px 0;">
|
||||
<a href="${signUrl}" style="background:#0066cc;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;font-size:13pt;">
|
||||
Accept Appointment & Sign
|
||||
</a>
|
||||
</p>
|
||||
<p>Or copy this link into your browser:<br/><span style="color:#0066cc;font-size:10pt;word-break:break-all;">${signUrl}</span></p>
|
||||
<p>Please note that until your acceptance is received, your appointment is not complete.</p>
|
||||
<p>Yours sincerely,<br/><strong>The Board of ${companyName}</strong></p>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
// ─── PDF conversion ───────────────────────────────────────────────────────
|
||||
|
||||
async function htmlToPdf(htmlPath: string): Promise<string> {
|
||||
const { spawnSync } = await import("child_process");
|
||||
const { existsSync, renameSync } = await import("fs");
|
||||
const pdfPath = htmlPath.replace(".html", ".pdf");
|
||||
const lo = "/Applications/LibreOffice.app/Contents/MacOS/soffice";
|
||||
const env = { ...process.env, HOME: "/Volumes/KIOXIA", PATH: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin", TMPDIR: "/tmp" };
|
||||
try {
|
||||
spawnSync(lo, ["--headless", "--convert-to", "pdf", "--outdir", "/tmp/", htmlPath], { timeout: 120, env });
|
||||
} catch {
|
||||
try {
|
||||
spawnSync("soffice", ["--headless", "--convert-to", "pdf", "--outdir", "/tmp/", htmlPath], { timeout: 120, env });
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
// macOS LibreOffice may write to /private/tmp even when --outdir /tmp/ is given
|
||||
if (!existsSync(pdfPath)) {
|
||||
const privatePath = pdfPath.replace("/tmp/", "/private/tmp/");
|
||||
if (existsSync(privatePath)) {
|
||||
renameSync(privatePath, pdfPath);
|
||||
}
|
||||
}
|
||||
return existsSync(pdfPath) ? pdfPath : "";
|
||||
}
|
||||
// ─── Email ───────────────────────────────────────────────────────────────
|
||||
|
||||
async function sendAppointmentEmail(opts: {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue