Skip to content

Slice 4 (Module 02): Doctor "Shared with me" view + document download #12

Closed

Description

@mGasiorek998

What to build

Give doctors a view of all documents shared with them and the ability to download any shared file. Expose the doctor-facing component as a named export so Module 04 (appointments) can embed it without reimplementing any document logic.

  • getSharedWithMe use case: returns documents shared with the authenticated doctor, grouped by owning patient (patientId, patientDisplayName, documents[]).
  • downloadDocument use case: verifies a document_shares row exists for (documentId, doctorId) before streaming — 403 ACCESS_DENIED if absent. Streams via createReadStream (no full-file buffering).
  • GET /documents/shared-with-me → 200 + Array<{ patientId, patientDisplayName, documents: Array<{ id, filename, mimeType, size, uploadedAt }> }> behind requireAuth() + requireRole(['doctor']). Note: this path must be registered before GET /documents/:id/file in the Hono router.
  • GET /documents/:id/file → 200 + binary stream (Content-Type: stored mimeType, Content-Disposition: attachment; filename="<original>") behind requireAuth() + requireRole(['doctor']). Returns 403 ACCESS_DENIED if not shared with this doctor; 404 DOCUMENT_NOT_FOUND if document does not exist.
  • Fill in these two endpoints in packages/contracts/src/documents.ts.
  • SharedDocumentsList React component (named export): self-contained, no required props, renders grouped documents with download links using the useSharedDocuments hook.
  • useSharedDocuments TanStack Query hook (named export): calls GET /documents/shared-with-me.
  • Both exports live under apps/web/src/features/documents/ and are importable by Module 04 without any document logic being reimplemented there.

Acceptance criteria

  • GET /documents/shared-with-me returns 200 with documents grouped by patient when shares exist; returns [] when no documents are shared with the doctor.
  • GET /documents/:id/file streams the file with correct Content-Type and Content-Disposition: attachment; filename="<original>" headers when the document is shared with the calling doctor.
  • GET /documents/:id/file returns 403 { error: "ACCESS_DENIED", ... } when no document_shares row exists for the (documentId, doctorId) pair; zero file bytes are written to the response.
  • GET /documents/:id/file returns 404 { error: "DOCUMENT_NOT_FOUND", ... } for an unknown document id.
  • Both endpoints return 401 without a valid JWT and 403 with a patient JWT.
  • SharedDocumentsList renders the grouped document list when imported and rendered in isolation (Vitest component test against a mocked contract).
  • useSharedDocuments is a named export callable from outside the documents feature folder.
  • pnpm verify exits 0.

User stories covered

Blocked by

Activity

  1. added
    afkEligible for the main agent loop
    sliceVertical tracer-bullet slice
    in-progressCurrently being worked on by an iteration (semaphore)
    on May 7, 2026
  2. mGasiorek998 commented on May 7, 2026

    @mGasiorek998
    Author

    Plan: two new doctor-side endpoints; the shared-with-me list path must be registered BEFORE /documents/:id/file so the param route doesn't shadow it.

    Files touched:

    • apps/api/src/infrastructure/document-repository.ts: listDocumentsSharedWithDoctor (join document_shares ↔ documents ↔ patients ↔ users), findDocumentShareForDoctor.
    • apps/api/src/use-cases/documents/get-shared-with-me.ts: returns grouped { patientId, patientDisplayName, documents[] }.
    • apps/api/src/use-cases/documents/download-document.ts: verifies (documentId, doctorId) is in document_shares before yielding stream + metadata.
    • apps/api/src/routes/documents/documents-router.ts: register both new routes with inline requireAuth + requireRole(['doctor']) (root /documents .use() is patient-only and does not match sub-paths anyway). Stream the file via Hono's Response with the existing storage.read() ReadableStream.
    • packages/contracts/src/documents.ts: contract entries for both endpoints.
    • apps/web/src/features/documents/use-shared-documents.ts: useSharedDocuments named-export hook.
    • apps/web/src/features/documents/SharedDocumentsList.tsx: self-contained named-export component (no required props), uses useSharedDocuments + apiClient base URL for download links.
    • apps/web/src/routes/doctor.tsx (or a new route): mount SharedDocumentsList for the doctor dashboard.

    Tests: integration AC1–AC5 in documents.test.ts under a new describeWithDb block; pre-seed two patients + two doctors + two docs + a couple of share rows; assertions on grouping, streaming headers + body, 403 unshared, 404 unknown id, auth/role guards. UI named-export component test deferred (no apps/web vitest infra yet — same convention as Slices 1–3).

  3. mGasiorek998 commented on May 7, 2026

    @mGasiorek998
    Author

    Light review advisory (warning, not blocker): SharedDocumentsList.tsx uses raw fetch for the binary download because the shared apiClient hardcodes response.json() and cannot return a Blob. The bypass is documented inline. Follow-up cleanup option (out of scope here): extend apiClient with a binary-response branch, or move the download helper into a feature-level fetch wrapper, so the architecture-rules ban on direct fetch in components has no exceptions.

  4. added
    qa-readyPR opened, awaiting human QA
    and removed
    in-progressCurrently being worked on by an iteration (semaphore)
    on May 7, 2026
  5. mGasiorek998 commented on May 7, 2026

    @mGasiorek998
    Author

    Implementation PR opened: #18. 1 red→green cycle, 9 new integration tests added (AC1–AC5). Backend covers GET /documents/shared-with-me (grouped) and GET /documents/:id/file (streamed) with the literal path registered before the param so ordering is correct. Web SharedDocumentsList + useSharedDocuments are named exports under apps/web/src/features/documents/, ready for Module 04 to import without reimplementing document logic. pnpm verify clean (77 tests).

  6. added a commit that references this issue on May 7, 2026
    0fde8f8
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