Repository navigation
Slice 1 (Module 02): Document upload & list — foundation #9
Copy link
Copy link
Closed
Labels
afkEligible for the main agent loopEligible for the main agent looppriority:2Priority 2Priority 2qa-readyPR opened, awaiting human QAPR opened, awaiting human QAsliceVertical tracer-bullet sliceVertical tracer-bullet slice
Description
What to build
Lay the full foundation for patient document management as a single end-to-end slice. This covers both new database tables, the storage service, the upload and list endpoints, the typed contract, and the patient-facing /patient/documents page.
Specifically:
- Add
documentsanddocument_sharestables to the Drizzle schema (both needed now so FK constraints are in place from the start). - Add
DOCUMENT_STORAGE_PATHenv var to the Zod config (defaults toapps/api/storage/documents/); auto-create the directory recursively at API startup; addapps/api/storage/to.gitignore. - Implement the document storage service (deep module):
write(stream) → uuid,read(uuid) → ReadStream,delete(uuid) → void. No DB knowledge. - Implement
uploadDocumentuse case: MIME allowlist check (application/pdf,image/png,image/jpeg,image/webp), write to disk, insertdocumentsrow, return metadata. - Implement
listDocumentsuse case: fetch patient's own documents ordered byuploadedAtdescending. - Apply Hono
bodyLimitmiddleware at 10 MiB + 1 byte on the upload route — oversized requests rejected beforeparseBody()runs, no partial file ever touches disk. POST /documents(201 + metadata | 413 | 415 | 422 | 500) andGET /documents(200 + array) routes, both behindrequireAuth()+requireRole(['patient']).- Fill in
packages/contracts/src/documents.tswith Zod schemas for these two endpoints. /patient/documentspage in the web app: file input that calls the upload endpoint, document list showing filename / size / uploadedAt for each entry.
Acceptance criteria
-
POST /documentswith a valid PDF ≤ 10 MiB returns 201 with{ id, filename, mimeType, size, uploadedAt }and a UUID-named file (no extension, no original filename) appears in the storage directory. -
POST /documentswith a file > 10 MiB returns 413{ error: "FILE_TOO_LARGE", ... }and no file is written to disk. -
POST /documentswith a disallowed MIME type returns 415{ error: "UNSUPPORTED_MEDIA_TYPE", ... }. -
POST /documentswith no file part returns 422{ error: "MISSING_FILE", ... }. -
GET /documentsreturns 200 with documents ordered byuploadedAtdescending; returns[]when the patient has no documents. - Both endpoints return 401 without a valid JWT and 403 when called with a doctor JWT.
- A fresh clone: after
cp .env.example .env && pnpm dev, the storage directory exists and uploads work without any manual mkdir. - Storage service unit tests pass against a real temp directory (cleaned up in
afterAll). - Repository integration tests pass against real Postgres.
- HTTP integration tests cover all status codes above.
-
pnpm verifyexits 0.
User stories covered
- US Slice 1: Monorepo scaffold + /health tracer bullet #1: As a patient, I want to upload a medical document so that I can make it available for sharing with my doctors.
- US Slice 2: Login + role-aware dashboard happy path #2: As a patient, I want to see a list of all my uploaded documents ordered from newest to oldest.
- US fix: api-client doubles base URL in fetch call #8: As a patient, I want to upload a file up to 10 MiB.
- US Slice 1 (Module 02): Document upload & list — foundation #9: As a patient, I want an oversized upload to be rejected before any data is written.
- US Slice 2 (Module 02): Delete document with atomic cascade #10: As a patient, I want unsupported file types to be rejected immediately.
- US Slice 1 (Module 03): Doctor profile read/update + specialization column #19: As a developer, I want
pnpm verifyto pass at every commit. - US Slice 2a (Module 03): Slot table + list-own + read-only slot management page #20: As a developer, I want the document storage directory created automatically at API startup.
- US Slice 2b (Module 03): Slot creation with overlap detection + add slot form #21: As a developer, I want the storage path configurable via env var.
Blocked by
None — can start immediately (requires auth slice to be merged first).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
afkEligible for the main agent loopEligible for the main agent looppriority:2Priority 2Priority 2qa-readyPR opened, awaiting human QAPR opened, awaiting human QAsliceVertical tracer-bullet sliceVertical tracer-bullet slice
Activity
mGasiorek998 commentedon May 7, 2026
Plan for Slice 1 (Module 02): Document upload & list — foundation
Modules touched:
apps/api/src/db/schema.ts— adddocuments+document_sharestablesapps/api/src/config.ts— addDOCUMENT_STORAGE_PATHenv var (defaultapps/api/storage/documents/)apps/api/src/infrastructure/document-storage.ts— storage service:write,read,deleteapps/api/src/infrastructure/document-repository.ts— Drizzle-backed insert + listapps/api/src/use-cases/documents/upload-document.ts— MIME check, disk write, DB insertapps/api/src/use-cases/documents/list-documents.ts— fetch patient docs ordered by uploadedAtapps/api/src/routes/documents/documents-router.ts— POST + GET with bodyLimit + requireAuth + requireRolepackages/contracts/src/documents.ts— Zod schemas for upload and list endpointsapps/web/src/features/documents/— query hook + componentapps/web/src/routes/patient/documents.tsx— patient documents pageTests planned (TDD):
Order: schema → config → storage service → repository → use-cases → routes → contract → web
mGasiorek998 commentedon May 7, 2026
Implementation PR opened: #15. 4 red→green cycles, 16 tests added (12 HTTP integration + 4 storage unit).
pnpm verifyexits 0 (68 tests total).