diff --git a/teressa-copeland-homes/src/app/api/documents/[id]/preview/route.ts b/teressa-copeland-homes/src/app/api/documents/[id]/preview/route.ts new file mode 100644 index 0000000..caafd17 --- /dev/null +++ b/teressa-copeland-homes/src/app/api/documents/[id]/preview/route.ts @@ -0,0 +1,73 @@ +import { auth } from '@/lib/auth'; +import { db } from '@/lib/db'; +import { documents, users, getFieldType } from '@/lib/db/schema'; +import type { SignatureFieldData } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; +import { preparePdf } from '@/lib/pdf/prepare-document'; +import path from 'node:path'; +import { readFile, unlink } from 'node:fs/promises'; + +const UPLOADS_DIR = path.join(process.cwd(), 'uploads'); + +export async function POST( + req: Request, + { params }: { params: Promise<{ id: string }> } +) { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + + const { id } = await params; + const body = await req.json() as { textFillData?: Record }; + + const doc = await db.query.documents.findFirst({ where: eq(documents.id, id) }); + if (!doc) return Response.json({ error: 'Not found' }, { status: 404 }); + if (!doc.filePath) return Response.json({ error: 'Document has no PDF file' }, { status: 422 }); + + const srcPath = path.join(UPLOADS_DIR, doc.filePath); + // Versioned preview path — never overwrites _prepared.pdf + const previewRelPath = doc.filePath.replace(/\.pdf$/, `_preview_${Date.now()}.pdf`); + const previewPath = path.join(UPLOADS_DIR, previewRelPath); + + // Path traversal guard + if (!previewPath.startsWith(UPLOADS_DIR)) { + return new Response('Forbidden', { status: 403 }); + } + + const sigFields = (doc.signatureFields as SignatureFieldData[]) ?? []; + const textFields = body.textFillData ?? {}; + + // Fetch agent's saved signature and initials + const agentUser = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { agentSignatureData: true, agentInitialsData: true }, + }); + const agentSignatureData = agentUser?.agentSignatureData ?? null; + const agentInitialsData = agentUser?.agentInitialsData ?? null; + + // 422 guard: agent-signature fields present but no signature saved + const hasAgentSigFields = sigFields.some(f => getFieldType(f) === 'agent-signature'); + if (hasAgentSigFields && !agentSignatureData) { + return Response.json( + { error: 'agent-signature-missing', message: 'No agent signature saved.' }, + { status: 422 } + ); + } + + // 422 guard: agent-initials fields present but no initials saved + const hasAgentInitialsFields = sigFields.some(f => getFieldType(f) === 'agent-initials'); + if (hasAgentInitialsFields && !agentInitialsData) { + return Response.json( + { error: 'agent-initials-missing', message: 'No agent initials saved.' }, + { status: 422 } + ); + } + + try { + await preparePdf(srcPath, previewPath, textFields, sigFields, agentSignatureData, agentInitialsData); + const pdfBytes = await readFile(previewPath); + return new Response(pdfBytes, { headers: { 'Content-Type': 'application/pdf' } }); + } finally { + // Fire-and-forget: always delete the temp preview file + unlink(previewPath).catch(() => {}); + } +}