Skip to content

Slice 1 (Module 02): Document upload & list — foundation #9

Description

@mGasiorek998

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 documents and document_shares tables to the Drizzle schema (both needed now so FK constraints are in place from the start).
  • Add DOCUMENT_STORAGE_PATH env var to the Zod config (defaults to apps/api/storage/documents/); auto-create the directory recursively at API startup; add apps/api/storage/ to .gitignore.
  • Implement the document storage service (deep module): write(stream) → uuid, read(uuid) → ReadStream, delete(uuid) → void. No DB knowledge.
  • Implement uploadDocument use case: MIME allowlist check (application/pdf, image/png, image/jpeg, image/webp), write to disk, insert documents row, return metadata.
  • Implement listDocuments use case: fetch patient's own documents ordered by uploadedAt descending.
  • Apply Hono bodyLimit middleware at 10 MiB + 1 byte on the upload route — oversized requests rejected before parseBody() runs, no partial file ever touches disk.
  • POST /documents (201 + metadata | 413 | 415 | 422 | 500) and GET /documents (200 + array) routes, both behind requireAuth() + requireRole(['patient']).
  • Fill in packages/contracts/src/documents.ts with Zod schemas for these two endpoints.
  • /patient/documents page in the web app: file input that calls the upload endpoint, document list showing filename / size / uploadedAt for each entry.

Acceptance criteria

  • POST /documents with 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 /documents with a file > 10 MiB returns 413 { error: "FILE_TOO_LARGE", ... } and no file is written to disk.
  • POST /documents with a disallowed MIME type returns 415 { error: "UNSUPPORTED_MEDIA_TYPE", ... }.
  • POST /documents with no file part returns 422 { error: "MISSING_FILE", ... }.
  • GET /documents returns 200 with documents ordered by uploadedAt descending; 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 verify exits 0.

User stories covered

Blocked by

None — can start immediately (requires auth slice to be merged first).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkEligible for the main agent looppriority:2Priority 2qa-readyPR opened, awaiting human QAsliceVertical tracer-bullet slice

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions