feat(20-01): extend POST /api/documents with documentTemplateId branch

- Parse documentTemplateId from JSON body alongside formTemplateId
- Fetch document template with formTemplate relation for PDF filename
- Copy signatureFields with fresh crypto.randomUUID per field (snapshot independence)
- Map template signer role labels to client email + contacts array
- Return early with inserted doc; existing form-library and upload paths unchanged
This commit is contained in:
Chandler Copeland
2026-04-06 14:50:24 -06:00
parent af5beaf5cb
commit bdf0cb02ff

View File

@@ -1,6 +1,7 @@
import { auth } from '@/lib/auth'; import { auth } from '@/lib/auth';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { documents, formTemplates } from '@/lib/db/schema'; import { documents, formTemplates, documentTemplates, clients } from '@/lib/db/schema';
import type { SignatureFieldData, ClientContact, DocumentSigner } from '@/lib/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { copyFile, mkdir, writeFile } from 'node:fs/promises'; import { copyFile, mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
@@ -8,6 +9,8 @@ import path from 'node:path';
const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms'); const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms');
const UPLOADS_DIR = path.join(process.cwd(), 'uploads'); const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
const SIGNER_COLORS = ['#6366f1', '#f43f5e', '#10b981', '#f59e0b'];
export async function POST(req: Request) { export async function POST(req: Request) {
const session = await auth(); const session = await auth();
if (!session) return new Response('Unauthorized', { status: 401 }); if (!session) return new Response('Unauthorized', { status: 401 });
@@ -17,6 +20,7 @@ export async function POST(req: Request) {
let clientId: string; let clientId: string;
let name: string; let name: string;
let formTemplateId: string | undefined; let formTemplateId: string | undefined;
let documentTemplateId: string | undefined;
let fileBuffer: ArrayBuffer | undefined; let fileBuffer: ArrayBuffer | undefined;
if (contentType.includes('multipart/form-data')) { if (contentType.includes('multipart/form-data')) {
@@ -32,6 +36,7 @@ export async function POST(req: Request) {
clientId = body.clientId; clientId = body.clientId;
name = body.name; name = body.name;
formTemplateId = body.formTemplateId; formTemplateId = body.formTemplateId;
documentTemplateId = body.documentTemplateId;
} }
if (!clientId || !name) { if (!clientId || !name) {
@@ -50,6 +55,66 @@ export async function POST(req: Request) {
await mkdir(destDir, { recursive: true }); await mkdir(destDir, { recursive: true });
// Document template branch — apply saved template with fresh field UUIDs
if (documentTemplateId) {
const docTemplate = await db.query.documentTemplates.findFirst({
where: eq(documentTemplates.id, documentTemplateId),
with: { formTemplate: true },
});
if (!docTemplate) return Response.json({ error: 'Document template not found' }, { status: 404 });
// Copy PDF from seeds dir using the form template's filename
const srcPath = path.join(SEEDS_DIR, docTemplate.formTemplate.filename);
await copyFile(srcPath, destPath);
// Copy fields with fresh UUIDs — hints preserved verbatim
const rawFields: SignatureFieldData[] = (docTemplate.signatureFields as SignatureFieldData[] | null) ?? [];
const copiedFields: SignatureFieldData[] = rawFields.map(f => ({
...f,
id: crypto.randomUUID(),
}));
// Collect unique role labels in order of first appearance
const seenRoles = new Set<string>();
const uniqueRoles: string[] = [];
for (const f of copiedFields) {
if (f.signerEmail && !seenRoles.has(f.signerEmail)) {
seenRoles.add(f.signerEmail);
uniqueRoles.push(f.signerEmail);
}
}
// Fetch client for email + contacts for role-to-email mapping
const client = await db.query.clients.findFirst({
where: eq(clients.id, clientId),
});
const clientEmails = [
client?.email,
...((client?.contacts as ClientContact[] | null) ?? []).map(c => c.email),
].filter(Boolean) as string[];
const mappedSigners: DocumentSigner[] = uniqueRoles.map((role, i) => ({
email: clientEmails[i] ?? role,
color: SIGNER_COLORS[i % SIGNER_COLORS.length],
}));
// Use the form template's ID for the DB insert
const resolvedFormTemplateId = docTemplate.formTemplateId;
const [doc] = await db.insert(documents).values({
id: docId,
clientId,
name,
formTemplateId: resolvedFormTemplateId,
filePath: relPath,
status: 'Draft',
signatureFields: copiedFields,
signers: mappedSigners.length > 0 ? mappedSigners : null,
}).returning();
return Response.json(doc, { status: 201 });
}
if (fileBuffer !== undefined) { if (fileBuffer !== undefined) {
// Custom upload // Custom upload
await writeFile(destPath, Buffer.from(fileBuffer)); await writeFile(destPath, Buffer.from(fileBuffer));