--- phase: 04-pdf-ingest plan: 02 subsystem: api tags: [next.js, api-routes, pdf, authentication, file-upload, path-traversal] # Dependency graph requires: - phase: 04-pdf-ingest provides: form_templates table + documents schema extension + seeds/forms/ directory - phase: 03-agent-portal-shell provides: auth pattern (auth() from @/lib/auth), db pattern, portal data layer provides: - GET /api/forms-library — authenticated form template list ordered by name - POST /api/documents — creates document record + copies seed PDF or writes custom upload to uploads/clients/{clientId}/{uuid}.pdf - GET /api/documents/[id]/file — authenticated PDF streaming with path traversal guard affects: [04-03, 04-04] # Tech tracking tech-stack: added: [] patterns: - node:fs/promises (copyFile, mkdir, writeFile, readFile) for local file operations — no cloud storage - Path traversal guard: destPath.startsWith(UPLOADS_DIR) before any fs write - uploads/ kept outside public/ — files never served as static assets - Relative paths stored in DB (clients/{clientId}/{uuid}.pdf) — not absolute paths - content-type header branching for multipart/form-data vs JSON in POST handler key-files: created: - teressa-copeland-homes/src/app/api/forms-library/route.ts - teressa-copeland-homes/src/app/api/documents/route.ts - teressa-copeland-homes/src/app/api/documents/[id]/file/route.ts modified: [] key-decisions: - "uploads/ directory at project root (not under public/) — PDFs never accessible without auth" - "Relative paths stored in DB (clients/{clientId}/{uuid}.pdf) — absolute paths would break on server move" - "Path traversal guard on both write (POST) and read (GET /file) — prevents directory escape via malicious clientId or filePath" - "content-type branching in POST — single endpoint handles both form-data (custom upload) and JSON (template copy)" patterns-established: - "File serving routes: always guard with startsWith(BASE_DIR) before readFile" - "File writing routes: always guard with startsWith(UPLOADS_DIR) before copyFile/writeFile" - "params is a Promise in Next.js 15 App Router dynamic routes — always await params before destructuring" requirements-completed: [DOC-01, DOC-02] # Metrics duration: 1min completed: 2026-03-20 --- # Phase 4 Plan 02: PDF API Routes Summary **Three authenticated Next.js API routes: forms library list, document creation with seed-copy or file-upload, and path-traversal-guarded PDF streaming — PDFs never exposed as public static assets** ## Performance - **Duration:** ~1 min - **Started:** 2026-03-20T03:36:04Z - **Completed:** 2026-03-20T03:37:09Z - **Tasks:** 2 - **Files modified:** 3 ## Accomplishments - Created GET /api/forms-library that returns authenticated JSON list of form_templates ordered by name - Created POST /api/documents that handles both template-copy (JSON body) and custom-upload (multipart/form-data) paths, writes to uploads/clients/{clientId}/{uuid}.pdf, inserts documents row - Created GET /api/documents/[id]/file that streams PDF bytes with Content-Type: application/pdf, guarded by path traversal check - All three routes return 401 for unauthenticated requests - uploads/ directory at project root (not under public/), so no file is accessible without going through auth ## Task Commits Each task was committed atomically: 1. **Task 1: GET /api/forms-library authenticated template list** - `e0f180c` (feat) 2. **Task 2: POST /api/documents and GET /api/documents/[id]/file routes** - `32e129c` (feat) **Plan metadata:** (docs commit below) ## Files Created/Modified - `teressa-copeland-homes/src/app/api/forms-library/route.ts` - Authenticated GET: queries form_templates ordered by name, returns JSON array - `teressa-copeland-homes/src/app/api/documents/route.ts` - Authenticated POST: branches on content-type, copies/writes PDF to uploads/, inserts documents row - `teressa-copeland-homes/src/app/api/documents/[id]/file/route.ts` - Authenticated GET: streams PDF from uploads/ with path traversal guard ## Decisions Made - uploads/ at project root not under public/ — PDFs only served through authenticated routes, never as static files - Relative paths stored in DB (e.g. `clients/abc/uuid.pdf`) — absolute paths break if the project moves; always join with UPLOADS_BASE at read time - Path traversal guard on both write and read operations — a malicious clientId like `../../etc` would be caught before any fs operation - content-type branching in single POST endpoint — keeps document creation API surface minimal for the UI in Plan 03 ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered None. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - All three API routes deployed and responding with correct auth behavior - Plan 03 (UI) can call POST /api/documents with clientId + formTemplateId (JSON) or clientId + file (multipart) - Plan 03 can call GET /api/forms-library to populate the modal selector - Plan 04 (document management) can call GET /api/documents/[id]/file to preview or download PDFs --- *Phase: 04-pdf-ingest* *Completed: 2026-03-20* ## Self-Check: PASSED - forms-library/route.ts: FOUND - documents/route.ts: FOUND - documents/[id]/file/route.ts: FOUND - 04-02-SUMMARY.md: FOUND - commit e0f180c: FOUND - commit 32e129c: FOUND