feat(05-01): add preparePdf utility and fields/prepare API routes
- Installed @cantoo/pdf-lib for server-side PDF mutation - Created src/lib/pdf/prepare-document.ts with preparePdf function using atomic tmp->rename write pattern - form.flatten() called before drawing signature rectangles - Created GET/PUT /api/documents/[id]/fields routes for signature field storage - Created POST /api/documents/[id]/prepare route that calls preparePdf and transitions status to Sent - Fixed pre-existing null check error in scripts/debug-inspect2.ts (Rule 3: blocking build) - Build compiles successfully with 2 new API routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
import { documents } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import type { SignatureFieldData } from '@/lib/db/schema';
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const session = await auth();
|
||||
if (!session) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const { id } = await params;
|
||||
const doc = await db.query.documents.findFirst({ where: eq(documents.id, id) });
|
||||
if (!doc) return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
|
||||
return Response.json(doc.signatureFields ?? []);
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const session = await auth();
|
||||
if (!session) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const { id } = await params;
|
||||
const fields: SignatureFieldData[] = await req.json();
|
||||
|
||||
const [updated] = await db
|
||||
.update(documents)
|
||||
.set({ signatureFields: fields })
|
||||
.where(eq(documents.id, id))
|
||||
.returning();
|
||||
|
||||
if (!updated) return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
return Response.json(updated.signatureFields ?? []);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
import { documents } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { preparePdf } from '@/lib/pdf/prepare-document';
|
||||
import path from 'node:path';
|
||||
|
||||
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) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const { id } = await params;
|
||||
const body = await req.json() as {
|
||||
textFillData?: Record<string, string>;
|
||||
assignedClientId?: string;
|
||||
};
|
||||
|
||||
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);
|
||||
// Prepared variant stored next to original with _prepared suffix
|
||||
const preparedRelPath = doc.filePath.replace(/\.pdf$/, '_prepared.pdf');
|
||||
const destPath = path.join(UPLOADS_DIR, preparedRelPath);
|
||||
|
||||
// Path traversal guard
|
||||
if (!destPath.startsWith(UPLOADS_DIR)) {
|
||||
return new Response('Forbidden', { status: 403 });
|
||||
}
|
||||
|
||||
const sigFields = (doc.signatureFields as import('@/lib/db/schema').SignatureFieldData[]) ?? [];
|
||||
const textFields = body.textFillData ?? {};
|
||||
|
||||
await preparePdf(srcPath, destPath, textFields, sigFields);
|
||||
|
||||
const [updated] = await db
|
||||
.update(documents)
|
||||
.set({
|
||||
preparedFilePath: preparedRelPath,
|
||||
textFillData: body.textFillData ?? null,
|
||||
assignedClientId: body.assignedClientId ?? doc.assignedClientId ?? null,
|
||||
status: 'Sent',
|
||||
sentAt: new Date(),
|
||||
})
|
||||
.where(eq(documents.id, id))
|
||||
.returning();
|
||||
|
||||
return Response.json(updated);
|
||||
}
|
||||
Reference in New Issue
Block a user