Consent Service Design¶
Overview¶
A standalone NestJS service that manages the full consent lifecycle for patients — consent types, versioned text, and consent records (decisions). Extracted from clinical-api to become a cross-cutting platform service used by clinical-api, payments, notifications, and any future service that needs consent gates.
Goals¶
- Consent types configurable per organisation (code, legal basis, required flag)
- Versioned consent text with locale support and effective dates
- Record patient consent decisions (granted, denied, withdrawn)
- Quick consent status checks (single and bulk) for other services
- Event-driven invalidation — publishes consent events for cache invalidation
- Migrate existing consent models from clinical-api in one go
Boundaries¶
Owned by consent service¶
- Consent type definitions (per-org, with code, display name, legal basis, required flag)
- Consent text versions (per type, per locale, with effective date)
- Consent records (patient decisions linked to specific text version)
- Consent status queries (current status per patient per type)
- Bulk consent checks (multiple types at once)
- Consent domain events (granted, denied, withdrawn)
NOT owned by consent service¶
- Patient data — references
patientIdwithout owning it - Enforcement — services decide what to do when consent is missing
- Organisation/product management
- The consent UI/presentation (products handle that)
Data Model¶
Own Prisma schema and MySQL database (consent).
ConsentType¶
| Field | Type | Notes |
|---|---|---|
| id | UUID v7 | Primary key |
| organisationId | string | Reference to org (not FK) |
| code | string | Machine-readable, e.g. ai_processing, data_sharing |
| displayName | string | Human-readable name |
| description | string | Nullable |
| legalBasis | string | e.g. consent, legitimate_interest, contract |
| required | boolean | Whether mandatory for service usage |
| createdAt | datetime | |
| updatedAt | datetime |
Unique constraint on (organisationId, code).
ConsentTextVersion¶
| Field | Type | Notes |
|---|---|---|
| id | UUID v7 | Primary key |
| consentTypeId | UUID v7 | FK to ConsentType |
| locale | string | e.g. en-GB, en-US |
| text | text | Consent text shown to patient |
| effectiveFrom | datetime | When this version becomes active |
| createdAt | datetime |
Multiple versions per type per locale. Current version = latest effectiveFrom in the past.
ConsentRecord¶
| Field | Type | Notes |
|---|---|---|
| id | UUID v7 | Primary key |
| organisationId | string | Reference to org |
| patientId | string | Reference to patient in clinical-api |
| consentTypeId | UUID v7 | FK to ConsentType |
| textVersionId | UUID v7 | FK to ConsentTextVersion |
| decision | enum | granted, denied, withdrawn |
| recordedAt | datetime | When decision was captured |
| recordedBy | string | Nullable, actor who captured it |
| createdAt | datetime |
No unique constraint — multiple records per patient per type form a history. Latest record determines current status.
API Endpoints¶
Consent queries (require valid platform token)¶
GET /v1/patients/{patientId}/consents — List current consent status per type
- Query:
?org_id=xxx(required) - Returns latest decision per consent type for the patient
GET /v1/patients/{patientId}/consents/{typeCode}/status — Check single consent
- Returns
{ granted: true/false, decision, recorded_at, text_version_id }
POST /v1/patients/{patientId}/consents — Record consent decision
- Body:
{ consent_type_id, text_version_id, decision, recorded_by? } - Publishes event on success
GET /v1/patients/{patientId}/consents/check — Bulk consent check
- Query:
?org_id=xxx&types=ai_processing,data_sharing - Returns:
{ consents: { ai_processing: true, data_sharing: false } }
Admin endpoints (require consents:admin permission or ADMIN_API_SECRET)¶
Consent Types:
POST /v1/admin/consent-types— Create type for an orgGET /v1/admin/consent-types— List types (filter byorg_id)GET /v1/admin/consent-types/{id}— Get type with current text versionsPATCH /v1/admin/consent-types/{id}— Update (display name, description, required)DELETE /v1/admin/consent-types/{id}— Soft delete (fails if records exist)
Consent Text Versions:
POST /v1/admin/consent-types/{typeId}/text-versions— Add new text versionGET /v1/admin/consent-types/{typeId}/text-versions— List versions
Health¶
GET /health/liveGET /health/ready
Events¶
Published to Redis pub/sub channel consent.events:
consent.granted—{ type: "consent.granted", patient_id, org_id, consent_type_code, timestamp }consent.denied— same shapeconsent.withdrawn— same shape
Services subscribe to invalidate cached consent status.
Caching Strategy¶
- Services cache consent status with 5-minute TTL (in-memory LRU)
- Consent service publishes invalidation events on every decision capture
- Synchronous calls for recording decisions and initial status fetches
- Bulk check endpoint optimized for services checking multiple consents at once
Project Structure¶
services/consent/
├── prisma/
│ └── schema.prisma
├── src/
│ ├── main.ts
│ ├── app.module.ts
│ ├── config/
│ │ ├── app-config.ts
│ │ └── config.module.ts
│ ├── prisma/
│ │ ├── prisma.service.ts # ConsentPrismaService (isolated client)
│ │ └── prisma.module.ts
│ ├── health/
│ ├── types/ # ConsentType CRUD + text versions
│ ├── records/ # ConsentRecord capture + queries
│ ├── status/ # Status checks (single + bulk)
│ ├── events/ # Consent domain events
│ └── admin/
│ └── admin-auth.guard.ts
├── test/
│ └── integration/
├── package.json
└── .env.example
Dependencies¶
@sa-platform/common— IdModule, RedisModule, CorrelationIdMiddleware, ProblemJsonFilter@sa-platform/tsconfig,@sa-platform/eslint-config- Own Prisma client:
../node_modules/.prisma/consent-client - Port: 3003
Changes to Clinical-API¶
- Remove ConsentType, ConsentTextVersion, ConsentRecord models from Prisma schema
- Remove
src/consents/module entirely (controller, service, specs, DTOs) - Remove admin consent controllers (
admin-consent-types.controller.ts,admin-consent-text.controller.ts) - Update admin.module.ts to remove consent controllers
- Remove consent-related integration tests
Migration Path¶
- Build consent service with all endpoints and tests
- Remove consent models, module, and admin controllers from clinical-api
- Update clinical-api integration tests (remove consent tests)
- Update CI to generate consent service Prisma client