Skip to content

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

  • 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)
  • Patient data — references patientId without 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

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 org
  • GET /v1/admin/consent-types — List types (filter by org_id)
  • GET /v1/admin/consent-types/{id} — Get type with current text versions
  • PATCH /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 version
  • GET /v1/admin/consent-types/{typeId}/text-versions — List versions

Health

  • GET /health/live
  • GET /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 shape
  • consent.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

  1. Build consent service with all endpoints and tests
  2. Remove consent models, module, and admin controllers from clinical-api
  3. Update clinical-api integration tests (remove consent tests)
  4. Update CI to generate consent service Prisma client