Files
red/teressa-copeland-homes/src/app/api/templates/[id]/file/route.ts
Chandler Copeland 275565c933 feat(19-01): create template API routes — file, fields, ai-prepare
- GET /api/templates/[id]/file: streams form PDF from seeds/forms/ with path traversal guard
- GET /api/templates/[id]/fields: returns signatureFields array (or []) from documentTemplates
- POST /api/templates/[id]/ai-prepare: runs AI field placement with null client context, writes to DB
- All routes: auth guard + isNull(archivedAt) soft-delete filter consistent with Phase 18
2026-04-06 13:10:02 -06:00

40 lines
1.3 KiB
TypeScript

import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { documentTemplates } from '@/lib/db/schema';
import { and, eq, isNull } from 'drizzle-orm';
import path from 'node:path';
import { readFile } from 'node:fs/promises';
const SEEDS_FORMS_DIR = path.join(process.cwd(), 'seeds', 'forms');
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 template = await db.query.documentTemplates.findFirst({
where: and(eq(documentTemplates.id, id), isNull(documentTemplates.archivedAt)),
with: { formTemplate: true },
});
if (!template?.formTemplate) return Response.json({ error: 'Not found' }, { status: 404 });
const filePath = path.join(SEEDS_FORMS_DIR, template.formTemplate.filename);
// Path traversal guard
if (!filePath.startsWith(SEEDS_FORMS_DIR)) return new Response('Forbidden', { status: 403 });
try {
const file = await readFile(filePath);
return new Response(file, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `inline; filename="${template.formTemplate.filename}"`,
},
});
} catch {
return Response.json({ error: 'Form PDF not found on disk' }, { status: 404 });
}
}