Skip to content

Slice 3 (Module 02): Per-doctor sharing management (grant / revoke / state) #11

Closed

Description

@mGasiorek998

What to build

Give patients fine-grained control over which doctors can access each document. This slice adds three endpoints — sharing state, grant, revoke — and wires them into a sharing panel on the /patient/documents page.

  • getSharingState use case: returns all doctors in the system, each with a hasAccess boolean for the given document.
  • grantAccess use case: upserts into document_shares (ON CONFLICT DO NOTHING — idempotent).
  • revokeAccess use case: deletes from document_shares where matching pair exists (no error if absent — idempotent).
  • GET /documents/:id/shares → 200 + Array<{ doctorId, displayName, hasAccess }> behind requireAuth() + requireRole(['patient']).
  • PUT /documents/:id/shares/:doctorId → 200 (grant) behind requireAuth() + requireRole(['patient']).
  • DELETE /documents/:id/shares/:doctorId → 200 (revoke) behind requireAuth() + requireRole(['patient']).
  • Fill in these three endpoints in packages/contracts/src/documents.ts.
  • Sharing panel on /patient/documents: per-document expandable list of all doctors with a toggle (grant/revoke). Ownership boundary enforced (patient can only manage their own documents).

Acceptance criteria

  • GET /documents/:id/shares returns all doctors with correct hasAccess flags; returns 404 for unknown document; returns 403 for documents not owned by the calling patient.
  • PUT /documents/:id/shares/:doctorId creates a document_shares row and returns 200; calling it again for the same pair returns 200 with exactly one row (no duplicate).
  • DELETE /documents/:id/shares/:doctorId removes the document_shares row and returns 200; calling it when no share exists also returns 200 (idempotent).
  • Both grant and revoke return 404 { error: "DOCTOR_NOT_FOUND", ... } when doctorId does not correspond to a user with the doctor role.
  • Both grant and revoke return 403 when the calling patient does not own the document.
  • All three endpoints return 401 without a valid JWT and 403 with a doctor JWT.
  • The sharing panel in the UI reflects live state and updates immediately after toggling a doctor.
  • 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: 3 new endpoints (GET /documents/:id/shares, PUT and DELETE /documents/:id/shares/:doctorId) wired with inline requireAuth + requireRole(['patient']) middleware (parent /documents .use() does not cover sub-paths, per Slice 2 precedent).

    Files touched:

    • apps/api/src/infrastructure/document-repository.ts: add findDocumentById, listDoctorAccessForDocument (left join), grantDocumentShare (ON CONFLICT DO NOTHING), revokeDocumentShare, isUserDoctor.
    • apps/api/src/use-cases/documents/: new getSharingState.ts, grantAccess.ts, revokeAccess.ts use cases (ownership check + doctor existence check before mutation).
    • apps/api/src/routes/documents/documents-router.ts: add three sub-path routes with inline middleware.
    • apps/api/src/routes/documents/documents.test.ts: add integration test blocks for AC1-AC6.
    • packages/contracts/src/documents.ts: add three contract entries with response schemas.
    • apps/web/src/features/documents/: useDocumentShares query hook, useGrantAccess + useRevokeAccess mutations, SharingPanel component.
    • apps/web/src/routes/patient/documents.tsx: integrate panel under each row (expandable).

    Order: 4 red→green cycles (GET, PUT, DELETE, UI). All API cycles touch the same backend; the UI cycle adds a Vitest component test against a mocked contract.

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

    @mGasiorek998
    Author

    Implementation PR opened: #17. 1 red→green cycle, 17 new integration tests added (AC1–AC6). Backend covers GET /documents/:id/shares, PUT and DELETE /documents/:id/shares/:doctorId with idempotent grant/revoke and DOCTOR_NOT_FOUND/FORBIDDEN/DOCUMENT_NOT_FOUND failure modes. Web SharingPanel exposes per-doctor Share/Shared toggle on /patient/documents; TanStack Query invalidation makes the panel reflect live state. pnpm verify clean (85 tests).

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