From 7ddb920467b28e928f88e732a9a4024ff5347b11 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Thu, 19 Mar 2026 21:38:37 -0600 Subject: [PATCH] docs(04-02): complete PDF API routes plan summary - Created 04-02-SUMMARY.md with task commits, decisions, and dependency graph - Updated STATE.md: position advanced to 04-02 complete, decisions added - Updated ROADMAP.md: phase 4 progress to 2/4 plans --- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 20 +-- .../phases/04-pdf-ingest/04-02-SUMMARY.md | 120 ++++++++++++++++++ 3 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/04-pdf-ingest/04-02-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index dcd40e5..cb2ba62 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -141,7 +141,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | 1. Foundation | 1/3 | Complete | 2026-03-19 | | 2. Marketing Site | 2/3 | In Progress| | | 3. Agent Portal Shell | 4/4 | Complete | 2026-03-19 | -| 4. PDF Ingest | 0/? | Not started | - | +| 4. PDF Ingest | 2/4 | In Progress| | | 5. PDF Fill and Field Mapping | 0/? | Not started | - | | 6. Signing Flow | 0/? | Not started | - | | 7. Audit Trail and Download | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 44aaa8f..455ede9 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,12 +3,12 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: unknown -last_updated: "2026-03-19T21:40:00Z" +last_updated: "2026-03-20T03:38:08.328Z" progress: - total_phases: 7 + total_phases: 4 completed_phases: 3 total_plans: 14 - completed_plans: 11 + completed_plans: 12 --- # Project State @@ -23,9 +23,9 @@ See: .planning/PROJECT.md (updated 2026-03-19) ## Current Position Phase: 4 of 7 (PDF Ingest) — In Progress -Plan: 04-01 complete (1 of 4 plans in phase complete) -Status: Plan 04-01 complete — form_templates table + documents schema extension + migration applied + npm run seed:forms working -Last activity: 2026-03-19 — Plan 04-01 complete: Drizzle form_templates table, documents schema extension (formTemplateId, filePath), migration 0002 applied, seed-forms.ts with npm run seed:forms +Plan: 04-02 complete (2 of 4 plans in phase complete) +Status: Plan 04-02 complete — GET /api/forms-library + POST /api/documents + GET /api/documents/[id]/file routes deployed +Last activity: 2026-03-20 — Plan 04-02 complete: three authenticated API routes for forms library, document creation (template copy or file upload), and PDF file streaming with path traversal guard Progress: [███████░░░] 70% @@ -54,6 +54,7 @@ Progress: [███████░░░] 70% | Phase 03-agent-portal-shell P03 | 9 | 3 tasks | 7 files | | Phase 03-agent-portal-shell P04 | 5 | 2 tasks | 3 files | | Phase 04-pdf-ingest P01 | 8 | 2 tasks | 7 files | +| Phase 04-pdf-ingest P02 | 1 | 2 tasks | 3 files | ## Accumulated Context @@ -92,6 +93,9 @@ Recent decisions affecting current work: - [Phase 04-pdf-ingest 04-01]: formTemplates table uses text PK (crypto.randomUUID()) — consistent with all other tables in schema.ts - [Phase 04-pdf-ingest 04-01]: formTemplateId and filePath are nullable — custom uploads have no template; legacy document rows need no file path - [Phase 04-pdf-ingest 04-01]: seed:forms uses onConflictDoUpdate on filename — filename is natural unique key for idempotent monthly sync re-runs +- [Phase 04-pdf-ingest]: uploads/ at project root not under public/ — PDFs never accessible as static files +- [Phase 04-pdf-ingest]: Relative paths stored in DB (clients/{clientId}/{uuid}.pdf) — absolute paths would break on server move +- [Phase 04-pdf-ingest]: Path traversal guard on both write (POST) and read (GET /file) — prevents directory escape via malicious clientId or filePath ### Pending Todos @@ -107,6 +111,6 @@ None yet. ## Session Continuity -Last session: 2026-03-19 -Stopped at: Completed 04-01-PLAN.md — form_templates table + documents schema extension (formTemplateId, filePath) + migration 0002 + npm run seed:forms +Last session: 2026-03-20 +Stopped at: Completed 04-02-PLAN.md — GET /api/forms-library + POST /api/documents + GET /api/documents/[id]/file routes Resume file: None diff --git a/.planning/phases/04-pdf-ingest/04-02-SUMMARY.md b/.planning/phases/04-pdf-ingest/04-02-SUMMARY.md new file mode 100644 index 0000000..e15860a --- /dev/null +++ b/.planning/phases/04-pdf-ingest/04-02-SUMMARY.md @@ -0,0 +1,120 @@ +--- +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