Skip to content

VIBE-221 Subscription Fulfilment Email - #155

Merged
junaidiqbalmoj merged 49 commits into
masterfrom
feature/VIBE-221-subscription-fulfilment-email
Dec 23, 2025
Merged

VIBE-221 Subscription Fulfilment Email#155
junaidiqbalmoj merged 49 commits into
masterfrom
feature/VIBE-221-subscription-fulfilment-email

Conversation

@junaidiqbalmoj

@junaidiqbalmoj junaidiqbalmoj commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

Jira link

https://tools.hmcts.net/jira/browse/VIBE-221

Change description

Add gov notifier notification email for both api and manual upload functionality

Summary by CodeRabbit

  • New Features

    • Email notification system for hearing-list publications via GOV.UK Notify — triggered from manual uploads and ingestion, with retries/backoff, audit logging, deduplication and status tracking.
  • Documentation

    • Implementation plan, specification, tasks, GitHub secrets guide and E2E notifications README added.
  • Tests

    • Extensive unit, integration and E2E tests and helpers for notification flows, Gov.Notify client, validation and polling.
  • Chores

    • Database migrations and schema additions, new notifications package/config, TS path/type updates, dotenv and CI env updates, express peer dependency bumps.

✏️ Tip: You can customize this high-level summary in your review settings.

github-actions Bot and others added 10 commits November 21, 2025 16:09
Created specification and implementation plan for event-driven GOV.UK Notify
email notification system with deduplication, retry logic, and audit logging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Created specification document with requirements and acceptance criteria
- Created tasks document with implementation checklist
- Includes database schema, Gov.Notify integration, notification service, and testing tasks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Specification document with data models and Gov.Notify integration
- Technical implementation plan with 10-phase approach
- Detailed task breakdown with 70+ actionable tasks
- Estimated effort: 4-6 days (27-43 hours)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- specification.md: Comprehensive system architecture and requirements
- plan.md: 7-phase implementation plan (13 hours)
- tasks.md: 30+ granular tasks with acceptance criteria
- Downloaded email notification mockup images

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Created comprehensive technical implementation plan (plan.md) for subscription fulfilment:
- Event-driven trigger system for publication notifications
- Integration with VIBE-192 subscription infrastructure
- Gov.Notify client integration with bilingual templates
- Deduplication logic and error handling
- Non-blocking batch processing architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The remote branch uses 'notifications' (plural) with better structure.
Removed the local 'notification' directory to avoid confusion.
@coderabbitai

coderabbitai Bot commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a notifications library and end-to-end notification flow: DB migrations and Prisma model for notification audit logs, GOV.UK Notify client and template config, subscription queries and orchestration, triggers from ingestion/manual upload, tests, E2E helpers, TypeScript path/types, CI/env updates, and documentation.

Changes

Cohort / File(s) Summary
Database Migrations & Prisma
apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql, apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql, apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql, libs/notifications/prisma/schema.prisma, libs/subscriptions/prisma/schema.prisma
Add notification_audit_log table (columns, PK, indexes), add gov_notify_id column/index, drop unique index, and link subscription ↔ notificationAuditLogs; adjust subscription indexes and mappings.
Notifications Package & Config
libs/notifications/package.json, libs/notifications/src/config.ts, libs/notifications/tsconfig.json, libs/notifications/src/index.ts
New @hmcts/notifications package, exports, prismaSchemas path export and tsconfig for build/declarations.
Gov.Notify Integration
libs/notifications/src/govnotify/govnotify-client.ts, libs/notifications/src/govnotify/govnotify-client.test.ts, libs/notifications/src/govnotify/template-config.ts
GOV.UK Notify client with retry/backoff, typed send/result shapes, template config/parameter builder, and unit tests for success/retry/failure cases.
Notification Service & Queries
libs/notifications/src/notification/notification-service.ts, libs/notifications/src/notification/notification-service.test.ts, libs/notifications/src/notification/notification-queries.ts, libs/notifications/src/notification/notification-queries.test.ts, libs/notifications/src/notification/subscription-queries.ts, libs/notifications/src/notification/subscription-queries.test.ts, libs/notifications/src/notification/validation.ts, libs/notifications/src/notification/validation.test.ts
Orchestrator to validate events, fetch subscriptions, per-user processing, create/update audit logs, deduplication/validation helpers, and comprehensive unit tests.
Type Declarations
libs/notifications/types/email/index.d.ts
Ambient TypeScript declarations for notifications-node-client.
Integration — Blob Ingestion
libs/api/src/blob-ingestion/repository/service.ts, libs/api/src/blob-ingestion/repository/service.test.ts
Fire-and-forget trigger to sendPublicationNotifications after artefact creation when location/list-type resolved; non-blocking with logging; tests updated.
Integration — Manual Upload
libs/admin-pages/src/pages/manual-upload-summary/index.ts, libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
Invoke notifications after manual upload completion; errors logged and do not block upload; tests extended for notification scenarios and locales.
Account & Subscriptions Repos
libs/account/src/repository/query.ts, libs/account/src/repository/query.test.ts, libs/subscriptions/src/repository/queries.ts, libs/subscriptions/src/repository/queries.test.ts
Add findUserById and findSubscriptionsByLocationId repository helpers and tests.
Schema Discovery
apps/postgres/src/schema-discovery.ts, apps/postgres/src/schema-discovery.test.ts
Add notifications Prisma schema path to discovery and update tests to expect three schemas.
E2E Test infra & Specs
e2e-tests/utils/notification-helpers.ts, e2e-tests/utils/api-auth-helpers.ts, e2e-tests/run-with-credentials.js, e2e-tests/package.json, e2e-tests/playwright.config.ts, e2e-tests/README-NOTIFICATIONS.md, e2e-tests/tests/api/blob-ingestion-notifications.spec.ts, e2e-tests/tests/manual-upload.spec.ts, e2e-tests/tests/*
Add helpers for creating test users/subscriptions, API auth token helper (Azure), dotenv loading, Playwright env injection for GOV.UK keys, and E2E specs validating notification flows and cleanup.
OAuth env rename & CI
libs/api/src/middleware/oauth-middleware.ts, libs/api/src/middleware/oauth-middleware.test.ts, .github/workflows/e2e.yml, docs/GITHUB_SECRETS_SETUP.md
Rename AZURE_CLIENT_ID → AZURE_API_CLIENT_ID across code/tests/CI/docs; add AZURE_API_CLIENT_SECRET and GOVUK_NOTIFY secrets to CI/docs.
TypeScript paths & Build
tsconfig.json
Add typeRoots entry and path alias @hmcts/notifications.
Peer dependency bumps
multiple libs/*/package.json (admin-pages, api, auth, cloud-native-platform, list-types/*, public-pages, simple-router, subscriptions, system-admin-pages, verified-pages, web-core)
Bump peer dependency express from ^5.1.0 → ^5.2.0 across packages.
Documentation & Planning
docs/tickets/VIBE-221/plan.md, docs/tickets/VIBE-221/specification.md, docs/tickets/VIBE-221/tasks.md, e2e-tests/README-NOTIFICATIONS.md, docs/GITHUB_SECRETS_SETUP.md
Add plan, specification, tasks, E2E testing instructions and GitHub secrets documentation for subscription fulfilment notifications.

Sequence Diagram(s)

sequenceDiagram
    participant BlobSvc as Blob Ingestion
    participant NotifSvc as Notification Service
    participant SubQuery as Subscription Queries
    participant AuditDB as Notification Audit Log (DB)
    participant EmailClient as GOV.UK Notify

    BlobSvc->>BlobSvc: create artefact
    BlobSvc->>NotifSvc: sendPublicationNotifications(publicationId, locationId, ...)
    NotifSvc->>SubQuery: findActiveSubscriptionsByLocation(locationId)
    SubQuery-->>NotifSvc: subscriptions[]

    loop per subscriber
        NotifSvc->>AuditDB: createNotificationAuditLog(Pending)
        NotifSvc->>NotifSvc: validate email & build template params
        alt valid email
            NotifSvc->>EmailClient: sendEmail(params)
            EmailClient-->>NotifSvc: success / failure
            alt success
                NotifSvc->>AuditDB: updateNotificationStatus(Sent, govNotifyId, sentAt)
            else failure
                NotifSvc->>AuditDB: updateNotificationStatus(Failed, errorMessage)
            end
        else invalid/missing
            NotifSvc->>AuditDB: updateNotificationStatus(Skipped, reason)
        end
    end

    NotifSvc-->>BlobSvc: aggregated result (total, sent, failed, skipped)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ChrisS1512
  • linusnorton

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.26% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective: implementing subscription fulfilment email notifications (VIBE-221), which is the primary feature across the entire changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/VIBE-221-subscription-fulfilment-email

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8accab9 and 04ddf7d.

📒 Files selected for processing (2)
  • libs/subscriptions/src/repository/queries.test.ts
  • libs/subscriptions/src/repository/queries.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/subscriptions/src/repository/queries.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use camelCase for TypeScript variables: userId, caseDetails, documentId
Use PascalCase for classes and interfaces: UserService, CaseRepository. NO I prefix.
Use kebab-case for file and directory names: user-service.ts, case-management/
Use boolean variable names with is/has/can prefix: isActive, hasAccess, canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add .js extension to relative imports in ES modules (e.g., import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; no any without justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs

Files:

  • libs/subscriptions/src/repository/queries.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/subscriptions/src/repository/queries.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source code using *.test.ts or *.spec.ts naming pattern

Files:

  • libs/subscriptions/src/repository/queries.test.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/subscriptions/src/repository/queries.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use CommonJS - ES modules only with import/export syntax

Files:

  • libs/subscriptions/src/repository/queries.test.ts
🧬 Code graph analysis (1)
libs/subscriptions/src/repository/queries.test.ts (1)
libs/subscriptions/src/repository/queries.ts (1)
  • findSubscriptionsByLocationId (14-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (2)
libs/subscriptions/src/repository/queries.test.ts (2)

10-10: LGTM!

The import follows the established patterns and includes the required .js extension for ES modules.


84-126: LGTM!

The test suite follows the established patterns in the file and adequately covers the new findSubscriptionsByLocationId function. The tests verify:

  • Correct Prisma query parameters (where clause and orderBy)
  • Successful retrieval with multiple subscriptions for the same location
  • Empty result handling

The mock data appropriately represents the use case of multiple users subscribed to the same location.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

🎭 Playwright E2E Test Results

196 tests   196 ✅  17m 43s ⏱️
 26 suites    0 💤
  1 files      0 ❌

Results for commit 04ddf7d.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (24)
libs/notifications/tsconfig.json (1)

1-11: Consider adding typeRoots for consistency.

While the root tsconfig.json includes ./libs/notifications/types in the global typeRoots, the sibling module libs/notification/tsconfig.json declares typeRoots locally. For consistency and explicit type resolution, consider adding:

 {
   "extends": "../../tsconfig.json",
   "compilerOptions": {
     "outDir": "./dist",
     "rootDir": "./src",
     "declaration": true,
-    "declarationMap": true
+    "declarationMap": true,
+    "typeRoots": ["../../node_modules/@types", "./types"]
   },
   "include": ["src/**/*", "types/**/*"],
   "exclude": ["**/*.test.ts", "**/*.spec.ts", "dist", "node_modules"]
 }
apps/postgres/prisma/migrations/20251201115015_govnotifier_notification/migration.sql (1)

7-32: LGTM with minor observation.

The notification_log table structure is well-designed with appropriate indexes for common query patterns. The indexes on user_id, publication_id, subscription_id, and created_at will support efficient lookups.

Minor observation: Consider whether location_id should also have an index if queries will filter or join by location.

docs/tickets/VIBE-221/tasks.md (1)

64-116: Markdown lint nits: bare URL + minor wording

The content is clear; only minor markdown/grammar nits if you care about passing markdownlint/LanguageTool cleanly:

  • Line 106: wrap the bare URL in <...> or markdown link to satisfy MD034.
  • Line 37: “10 second timeout” → “10-second timeout” if you want to follow the hyphenation suggestion.
  • Line 105: “populated correctly … correctly” – consider dropping one “correctly” to avoid repetition.

All optional and non-blocking.

libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)

111-137: Notification trigger is awaited, and logging may expose too much detail

A few points around this notification block:

  1. Not truly fire-and-forget

    • Because sendPublicationNotifications is awaited, the POST response is delayed until the whole notification run completes. That’s at odds with the “fire-and-forget” / non‑blocking requirement described in the VIBE-221 docs and could hurt UX if there are many subscribers or Notify is slow.
    • Consider kicking this off without awaiting the result, e.g.:
      • void sendPublicationNotifications(...).then(/* log summary */).catch(/* log error */);
      • This keeps the upload flow latency bound to artefact creation and file save, not email sending.
  2. Log contents and PII

    • console.log of the full notificationResult object and console.error with the raw notificationError risk leaking PII (email addresses, full Notify payloads, etc.) depending on how the notification module structures those objects.
    • Safer pattern is to log only high‑level, non‑PII fields (artefactId, locationId, counts, maybe a correlation/reference ID) and, for errors, name/message plus a sanitized error code instead of the full error object.
    • Suggest constraining logs here to a small, explicit shape and relying on the notification module itself for any deeper debug logging.
  3. listTypeName field

    • Here you use listType?.name || \LIST_TYPE_${listTypeId}`whereas the rest of the file usesenglishFriendlyName/welshFriendlyName. Please double‑check that mockListTypesactually exposes aname` field and that it’s the intended value for the email template; otherwise you may want to reuse the same friendly name logic for consistency.

These are mostly design/privacy points rather than correctness, but they’re worth tightening before release.

docs/VIBE-221/tasks.md (1)

771-815: Markdown headings vs emphasis (MD036)

In the “Progress Tracking” section the phase labels are bolded (**Phase 1: Database Schema**, etc.). markdownlint flags this as “emphasis used instead of a heading” (MD036). If you want a clean lint run, consider turning these into proper headings, e.g.:

### Phase 1: Database Schema

and similarly for the other phases. Content is otherwise fine.

libs/notifications/src/notification/notification-queries.test.ts (1)

1-97: Tests correctly assert Prisma interaction; consider one extra assertion

The mocks and expectations line up with the implementation:

  • createNotificationAuditLog returns the mocked record and defaults status to "Pending".
  • updateNotificationStatus and findExistingNotification assertions match the Prisma calls, including the composite userId_publicationId key and errorMessage: undefined.

If you want slightly stronger coverage, you could also assert on the arguments passed to prisma.notificationAuditLog.create (status defaulting) in the first test, but as is this is already adequate.

VIBE-221-plan.md (1)

238-262: Add language to fenced block for markdownlint (MD040)

markdownlint flags the email‑template block for lacking a language (MD040). Since this is prose, using text (or md) is enough to satisfy it:

```text
Subject: New hearing list published: ((hearing_list_name))
...

Purely a documentation nit; behavior is unaffected.

</blockquote></details>
<details>
<summary>libs/notification/src/notifications-node-client.d.ts (1)</summary><blockquote>

`1-26`: **Duplicate NotifyClient typings and default export shape**  

Two small points on this declaration:

1. **Duplication**  
   - There are already NotifyClient typings elsewhere (`types/notifications-node-client.d.ts` / `libs/notification/types/notifications-node-client.d.ts`). To avoid these drifting out of sync, consider having a single canonical definition and re‑using it (e.g. via `declare module "notifications-node-client"` that re‑exports that type), rather than maintaining multiple copies.

2. **Default export shape**  
   - `export default { NotifyClient };` declares the default as an object with a `NotifyClient` property. If the real package exports a class as its default (or only uses a named export), this may not accurately match the runtime shape, and could mislead consumers using `import NotifyClient from "notifications-node-client";`.  
   - If the project only ever uses `import { NotifyClient } ...`, you can probably drop the default export from this declaration to keep types closer to reality.

Neither is breaking right now, but consolidating the typings and aligning the default export with actual usage will reduce confusion later.

</blockquote></details>
<details>
<summary>apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1)</summary><blockquote>

`8-34`: **Notification audit table design looks consistent; consider status tightening**

The `notification_audit_log` shape (UUID PK, timestamps, unique `(user_id, publication_id)` index, FK to `subscription`) looks aligned with a per-user-per-publication audit trail. If you want to prevent typos or drift in `status`, consider normalising it (enum / CHECK constraint), otherwise this is fine as-is.

</blockquote></details>
<details>
<summary>libs/notification/prisma/schema.prisma (1)</summary><blockquote>

`1-18`: **NotificationLog schema matches conventions; indexes look sensible**

Model naming and `@map` / `@@map` usage align with the Prisma/DB naming guidelines, and the chosen indexes support common access patterns by user, subscription, publication, and time. If you anticipate frequent queries by status (e.g. failed/pending retries), adding an index on `status` later may help, but not required now.

</blockquote></details>
<details>
<summary>docs/VIBE-221/plan.md (1)</summary><blockquote>

`37-47`: **Add a language to the project-structure code fence**

The file tree block is fenced with ``` but no language, which markdownlint (MD040) flags. Using something like:

```markdown
```text
libs/notification/
├── package.json
…

will keep the rendering the same while satisfying the linter.

</blockquote></details>
<details>
<summary>docs/VIBE-221/specification.md (1)</summary><blockquote>

`41-48`: **Tidy fenced blocks and bare URL to satisfy markdownlint**

A few small tweaks will make this spec pass markdownlint:

- Architecture diagram and GOV.UK Notify template snippet: add an explicit language, e.g.

  ```markdown
  ```text
  Publication Event → …

and

```markdown
```text
Your subscription has been triggered…

- GOV.UK Notify docs link: wrap as a Markdown link instead of a bare URL, e.g.

```markdown
- Documentation: [GOV.UK Notify Node client](https://docs.notifications.service.gov.uk/node.html)

These keep the content unchanged but clear MD040/MD034.

Also applies to: 509-509, 648-657

libs/notifications/src/notification/notification-service.test.ts (1)

1-193: Good coverage of happy-path and edge cases; consider adding failure-path assertions

The suite exercises the main flows (multi-subscriber send, duplicate suppression, invalid email skip, no-subscribers, invalid event) with a clean mocking pattern. To harden it further, you could:

  • Add a test where sendEmail rejects or returns a failure, asserting failed and errors are populated and updateNotificationStatus is called appropriately.
  • Optionally assert that findActiveSubscriptionsByLocation / createNotificationAuditLog are invoked with the expected arguments.

Not mandatory, but would make regressions in error handling more visible.

libs/notifications/src/notification/validation.ts (1)

1-50: Validation logic is sound; optional check for invalid Date values

The email validator and validatePublicationEvent cover the basic required fields and are consistent with how the service uses them. If you expect publicationDate to sometimes be an invalid Date object (e.g. new Date("bad")), you could strengthen that check with:

if (!event.publicationDate || Number.isNaN(event.publicationDate.getTime())) {
  errors.push("Publication date is required");
}

Otherwise this is fine as-is.

libs/notifications/src/notification/subscription-queries.ts (1)

1-31: Subscription query is correct; consider future filters for email status

The Prisma query and returned SubscriptionWithUser shape look correct and comply with the parameterisation guidelines. As the user model matures, you may want to extend the where clause to filter on flags like emailVerified / isActive (as outlined in the spec’s optimised SQL example) so invalid or inactive accounts are excluded at source.

VIBE-221-specification.md (1)

1-342: LGTM! Comprehensive specification document.

The specification is well-structured and provides clear technical requirements, event flow, error handling scenarios, and test cases. The document will serve as a valuable reference for implementation and testing.

Optional: Add language specifiers to fenced code blocks.

For improved markdown rendering and syntax highlighting, consider adding language specifiers to fenced code blocks (e.g., ```sql, ```typescript, ```text). The bare URL on line 70 could also be wrapped in angle brackets or a markdown link for consistency.

Apply static analysis suggestions if desired:

  • Lines 49, 60, 66, 107, 157, 263: Add language specifiers
  • Line 70: Wrap URL in markdown link format
libs/notification/src/notification-service.ts (1)

131-154: Consider extracting template parameter building logic.

The inline date formatting and list type lookup logic makes the function harder to read. Consider extracting this into a dedicated helper function like buildTemplateParameters.

Extract template parameter construction into a separate helper function to improve readability and testability.

function buildTemplateParameters(user: User, hearingListName: string, publicationDate: string, location: Location, baseUrl: string) {
  const listType = mockListTypes.find((lt) => lt.name === hearingListName);
  const listTypeFriendlyName = listType?.englishFriendlyName || hearingListName;

  const date = new Date(publicationDate);
  const day = String(date.getDate()).padStart(2, "0");
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const year = date.getFullYear();
  const formattedDate = `${day}/${month}/${year}`;

  return {
    ListType: listTypeFriendlyName,
    content_date: formattedDate,
    locations: location.name,
    start_page_link: baseUrl,
    subscription_page_link: `${baseUrl}/account-home`
  };
}

Then use it in the main function:

         const baseUrl = process.env.BASE_URL || "https://localhost:8080";
-
-        // Convert list type name to friendly name
-        const listType = mockListTypes.find((lt) => lt.name === hearingListName);
-        const listTypeFriendlyName = listType?.englishFriendlyName || hearingListName;
-
-        // Format date to dd/mm/yyyy
-        const date = new Date(publicationDate);
-        const day = String(date.getDate()).padStart(2, "0");
-        const month = String(date.getMonth() + 1).padStart(2, "0");
-        const year = date.getFullYear();
-        const formattedDate = `${day}/${month}/${year}`;
-
+        const personalisation = buildTemplateParameters(user, hearingListName, publicationDate, location, baseUrl);
         const client = await getNotifyClient();
         const response = await client.sendEmail(GOVUK_NOTIFY_TEMPLATE_ID, user.email, {
-          personalisation: {
-            ListType: listTypeFriendlyName,
-            content_date: formattedDate,
-            locations: location.name,
-            start_page_link: baseUrl,
-            subscription_page_link: `${baseUrl}/account-home`
-          },
+          personalisation,
           reference: `${publicationId}-${subscription.subscriptionId}`
         });
docs/tickets/VIBE-221/plan.md (1)

1-544: LGTM! Comprehensive technical plan.

The plan provides:

  • Clear architecture overview with module structure
  • Detailed database schema with proper snake_case naming
  • Well-defined component responsibilities
  • Comprehensive error handling scenarios
  • Thorough testing strategy
  • Security and compliance considerations
  • Performance optimization guidelines
  • Open questions that need clarification

The plan aligns well with the specification and provides solid guidance for implementation.

Optional: Address markdown linting issues.

Similar to the specification document, consider adding language specifiers to fenced code blocks and wrapping bare URLs in markdown links for better rendering.

libs/notifications/prisma/schema.prisma (1)

11-27: Consider using enum for status field.

Line 16 uses String for the status field, but the specification defines specific status values ("Pending", "Sent", "Failed", "Skipped"). Using a Prisma enum would provide better type safety and prevent invalid status values.

Define a Prisma enum for notification status to improve type safety:

+enum NotificationStatus {
+  PENDING
+  SENT
+  FAILED
+  SKIPPED
+}
+
 model NotificationAuditLog {
   notificationId String    @id @default(uuid()) @map("notification_id") @db.Uuid
   subscriptionId String    @map("subscription_id") @db.Uuid
   userId         String    @map("user_id") @db.Uuid
   publicationId  String    @map("publication_id") @db.Uuid
-  status         String    @default("Pending")
+  status         NotificationStatus @default(PENDING)
   errorMessage   String?   @map("error_message")
   createdAt      DateTime  @default(now()) @map("created_at")
   sentAt         DateTime? @map("sent_at")

This aligns with the VIBE-221 specification which defines an enum for NotificationStatus.

libs/notifications/src/govnotify/govnotify-client.ts (1)

23-43: Consider caching the NotifyClient instance.

A new NotifyClient is instantiated on every email send. While acceptable for low-volume usage, consider extracting client creation if this becomes a performance concern at scale.

libs/notifications/src/notification/notification-service.ts (1)

98-112: Redundant database operations when skipping notifications.

The code creates an audit log with status "Skipped" then immediately updates the same record to "Skipped" with an error message. This results in two unnecessary database round-trips.

Consider passing the error message during creation, or modifying createNotificationAuditLog to accept an optional error message:

     if (!subscription.user.email) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "No email address"
       });
-
-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "No email address");

       return {
         status: "skipped",
         error: `User ${subscription.userId}: No email address`
       };
     }

This would require updating CreateNotificationData interface to include an optional errorMessage field.

libs/notification/src/repository/queries.ts (3)

4-10: Export the interface for consumer type safety.

CreateNotificationLogParams is not exported, which means callers cannot reference this type when constructing parameters.

-interface CreateNotificationLogParams {
+export interface CreateNotificationLogParams {
   subscriptionId: string;
   userId: string;
   publicationId: string;
   locationId: string;
   status: string;
 }

30-49: Inconsistent status string casing between modules.

This file uses uppercase status values ("SENT", "FAILED") while notification-queries.ts uses title case ("Sent", "Failed", "Pending", "Skipped"). If these feed into the same reporting or query system, this inconsistency could cause issues.

Consider extracting status constants to a shared module:

// notification-status.ts
export const NotificationStatus = {
  PENDING: "Pending",
  SENT: "Sent",
  FAILED: "Failed",
  SKIPPED: "Skipped"
} as const;

51-63: Add explicit return types for public API functions.

Both findNotificationLogsByPublicationId and findNotificationLogsByUserId rely on inferred return types. Adding explicit types improves API clarity and catches accidental changes.

-export async function findNotificationLogsByPublicationId(publicationId: string) {
+export async function findNotificationLogsByPublicationId(publicationId: string): Promise<NotificationLog[]> {
   return prisma.notificationLog.findMany({
     where: { publicationId },
     orderBy: { createdAt: "desc" }
   });
 }

-export async function findNotificationLogsByUserId(userId: string) {
+export async function findNotificationLogsByUserId(userId: string): Promise<NotificationLog[]> {
   return prisma.notificationLog.findMany({
     where: { userId },
     orderBy: { createdAt: "desc" }
   });
 }

You'll need to define or import a NotificationLog interface matching the Prisma model.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc9c436 and 0f83f5e.

⛔ Files ignored due to path filters (3)
  • docs/VIBE-221/email notification mock up.png is excluded by !**/*.png
  • docs/VIBE-221/email notification template .png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (52)
  • .ai/plans/VIBE-221/plan.md (1 hunks)
  • .ai/plans/VIBE-221/specification.md (1 hunks)
  • .ai/plans/VIBE-221/tasks.md (1 hunks)
  • VIBE-221-plan.md (1 hunks)
  • VIBE-221-specification.md (1 hunks)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1 hunks)
  • apps/postgres/prisma/migrations/20251201115015_govnotifier_notification/migration.sql (1 hunks)
  • apps/postgres/src/schema-discovery.test.ts (1 hunks)
  • apps/postgres/src/schema-discovery.ts (1 hunks)
  • docs/VIBE-221/plan.md (1 hunks)
  • docs/VIBE-221/specification.md (1 hunks)
  • docs/VIBE-221/tasks.md (1 hunks)
  • docs/tickets/VIBE-221/plan.md (1 hunks)
  • docs/tickets/VIBE-221/specification.md (1 hunks)
  • docs/tickets/VIBE-221/tasks.md (1 hunks)
  • libs/account/src/repository/query.test.ts (2 hunks)
  • libs/account/src/repository/query.ts (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts (2 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
  • libs/notification/package.json (1 hunks)
  • libs/notification/prisma/schema.prisma (1 hunks)
  • libs/notification/src/config.ts (1 hunks)
  • libs/notification/src/index.ts (1 hunks)
  • libs/notification/src/notification-service.test.ts (1 hunks)
  • libs/notification/src/notification-service.ts (1 hunks)
  • libs/notification/src/notifications-node-client.d.ts (1 hunks)
  • libs/notification/src/repository/queries.test.ts (1 hunks)
  • libs/notification/src/repository/queries.ts (1 hunks)
  • libs/notification/tsconfig.json (1 hunks)
  • libs/notification/types/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/package.json (1 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/config.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/govnotify/template-config.ts (1 hunks)
  • libs/notifications/src/index.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
  • libs/notifications/src/notification/subscription-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/subscription-queries.ts (1 hunks)
  • libs/notifications/src/notification/validation.test.ts (1 hunks)
  • libs/notifications/src/notification/validation.ts (1 hunks)
  • libs/notifications/src/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/tsconfig.json (1 hunks)
  • libs/notifications/types/notifications-node-client.d.ts (1 hunks)
  • libs/subscriptions/prisma/schema.prisma (1 hunks)
  • libs/subscriptions/src/repository/queries.ts (1 hunks)
  • tsconfig.json (2 hunks)
  • types/notifications-node-client.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • apps/postgres/src/schema-discovery.ts
  • libs/account/src/repository/query.ts
  • libs/notification/src/index.ts
  • libs/notifications/src/notification/validation.test.ts
  • libs/subscriptions/src/repository/queries.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
  • libs/account/src/repository/query.test.ts
  • libs/notifications/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notification/src/notification-service.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/types/notifications-node-client.d.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notification/types/notifications-node-client.d.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • types/notifications-node-client.d.ts
  • libs/notifications/src/notification/subscription-queries.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notification/src/repository/queries.ts
  • apps/postgres/src/schema-discovery.test.ts
  • libs/notifications/src/index.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • apps/postgres/src/schema-discovery.ts
  • libs/account/src/repository/query.ts
  • libs/notification/src/index.ts
  • libs/notifications/src/notification/validation.test.ts
  • libs/subscriptions/src/repository/queries.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
  • libs/account/src/repository/query.test.ts
  • libs/notifications/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notification/src/notification-service.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/types/notifications-node-client.d.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notification/types/notifications-node-client.d.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • types/notifications-node-client.d.ts
  • libs/notifications/src/notification/subscription-queries.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notification/src/repository/queries.ts
  • apps/postgres/src/schema-discovery.test.ts
  • libs/notifications/src/index.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notifications/src/notification/validation.test.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/account/src/repository/query.test.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • apps/postgres/src/schema-discovery.test.ts
libs/*/src/config.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate config.ts file to avoid circular dependencies during Prisma client generation. Apps import config using the /config path.

Files:

  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
libs/*/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Files:

  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
**/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

TypeScript must use strict mode with no any without justification.

Files:

  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
  • tsconfig.json
libs/*/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

libs/*/package.json: Package names must use @hmcts scope (e.g., @hmcts/auth, @hmcts/case-management).
Module package.json must include both main export (.) and config export (./config) in the exports field, with production and default conditions.
Module package.json build script must include build:nunjucks if the module contains Nunjucks templates in the pages/ directory.
All test packages must use "test": "vitest run" script to run tests.

Files:

  • libs/notification/package.json
  • libs/notifications/package.json
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • libs/notifications/prisma/schema.prisma
  • libs/notification/prisma/schema.prisma
  • libs/subscriptions/prisma/schema.prisma
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
🧠 Learnings (13)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/src/config.ts : Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate `config.ts` file to avoid circular dependencies during Prisma client generation. Apps import config using the `/config` path.

Applied to files:

  • apps/postgres/src/schema-discovery.ts
  • libs/notification/src/config.ts
  • libs/notifications/tsconfig.json
  • libs/notifications/src/config.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/tsconfig.json
  • tsconfig.json
📚 Learning: 2025-11-27T09:48:13.010Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 136
File: libs/api/src/blob-ingestion/validation.ts:156-163
Timestamp: 2025-11-27T09:48:13.010Z
Learning: In libs/api/src/blob-ingestion/validation.ts, the permissive date validation in isValidISODate and isValidISODateTime functions is expected behavior and should not be flagged for stricter validation.

Applied to files:

  • libs/notifications/src/notification/validation.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • libs/notifications/src/notification/validation.test.ts
  • libs/notifications/tsconfig.json
  • libs/account/src/repository/query.test.ts
  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : All test packages must use `"test": "vitest run"` script to run tests.

Applied to files:

  • libs/notifications/src/notification/validation.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Use workspace aliases (`hmcts/*`) for imports instead of relative paths across packages.

Applied to files:

  • libs/notification/src/config.ts
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Always add `.js` extension to relative imports (e.g., `import { foo } from "./bar.js"`). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.

Applied to files:

  • libs/notification/src/config.ts
  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Applied to files:

  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/tsconfig.json : TypeScript must use strict mode with no `any` without justification.

Applied to files:

  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.

Applied to files:

  • libs/notifications/tsconfig.json
  • libs/notification/tsconfig.json
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Module package.json must include both main export (`.`) and config export (`./config`) in the exports field, with production and default conditions.

Applied to files:

  • libs/notification/package.json
  • libs/notifications/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Package names must use hmcts scope (e.g., `hmcts/auth`, `hmcts/case-management`).

Applied to files:

  • libs/notification/package.json
  • libs/notifications/package.json
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to package.json : All package.json files must use `"type": "module"` to enforce ES Modules.

Applied to files:

  • libs/notification/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Module package.json build script must include `build:nunjucks` if the module contains Nunjucks templates in the `pages/` directory.

Applied to files:

  • libs/notification/package.json
  • libs/notifications/package.json
🧬 Code graph analysis (15)
libs/notifications/src/notification/validation.test.ts (1)
libs/notifications/src/notification/validation.ts (2)
  • isValidEmail (3-8)
  • validatePublicationEvent (23-50)
libs/notifications/src/notification/notification-service.test.ts (4)
libs/notifications/src/notification/subscription-queries.ts (1)
  • findActiveSubscriptionsByLocation (14-31)
libs/notifications/src/notification/notification-queries.ts (2)
  • findExistingNotification (45-56)
  • createNotificationAuditLog (21-32)
libs/notifications/src/index.ts (1)
  • sendPublicationNotifications (1-1)
libs/notifications/src/notification/notification-service.ts (1)
  • sendPublicationNotifications (21-86)
libs/account/src/repository/query.test.ts (1)
libs/account/src/repository/query.ts (1)
  • findUserById (18-22)
libs/notifications/src/notifications-node-client.d.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notification/src/repository/queries.test.ts (1)
libs/notification/src/repository/queries.ts (3)
  • createNotificationLog (12-28)
  • updateNotificationLogSent (30-38)
  • updateNotificationLogFailed (40-49)
libs/notifications/src/govnotify/govnotify-client.ts (2)
libs/notifications/src/govnotify/template-config.ts (3)
  • TemplateParameters (32-39)
  • getApiKey (12-17)
  • getTemplateId (5-10)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notification/subscription-queries.test.ts (1)
libs/notifications/src/notification/subscription-queries.ts (1)
  • findActiveSubscriptionsByLocation (14-31)
libs/notifications/types/notifications-node-client.d.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notification/src/notifications-node-client.d.ts (2)
libs/notification/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-23)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notification/notification-service.ts (5)
libs/notifications/src/notification/validation.ts (3)
  • PublicationEvent (10-16)
  • validatePublicationEvent (23-50)
  • isValidEmail (3-8)
libs/notifications/src/notification/subscription-queries.ts (1)
  • SubscriptionWithUser (3-12)
libs/notifications/src/notification/notification-queries.ts (3)
  • findExistingNotification (45-56)
  • createNotificationAuditLog (21-32)
  • updateNotificationStatus (34-43)
libs/notifications/src/govnotify/template-config.ts (1)
  • buildTemplateParameters (41-54)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)
libs/notifications/src/notification/notification-queries.test.ts (1)
libs/notifications/src/notification/notification-queries.ts (3)
  • createNotificationAuditLog (21-32)
  • updateNotificationStatus (34-43)
  • findExistingNotification (45-56)
libs/notifications/src/govnotify/govnotify-client.test.ts (1)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)
types/notifications-node-client.d.ts (3)
libs/notification/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-23)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (3)
libs/publication/src/index.ts (1)
  • mockListTypes (1-1)
libs/list-types/common/src/index.ts (1)
  • mockListTypes (2-2)
libs/notification/src/notification-service.ts (1)
  • sendPublicationNotifications (38-236)
apps/postgres/src/schema-discovery.test.ts (1)
apps/postgres/src/schema-discovery.ts (1)
  • getPrismaSchemas (6-8)
🪛 LanguageTool
docs/VIBE-221/tasks.md

[grammar] ~158-~158: Ensure spelling is correct
Context: ...NotificationEmail()function 4. DefineEmailPersonalisationinterface 5. DefineSendEmailResult` i...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/tickets/VIBE-221/tasks.md

[grammar] ~37-~37: Use a hyphen to join words.
Context: ...] Add timeout handling for API calls (10 second timeout) - [x] Create template co...

(QB_NEW_EN_HYPHEN)


[style] ~105-~105: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...erify all template parameters populated correctly (user_name, hearing_list_name, publicat...

(ADVERB_REPETITION_PREMIUM)

docs/tickets/VIBE-221/plan.md

[style] ~484-~484: It’s more common nowadays to write this noun as one word.
Context: ... table with email field?) - Where is user name stored for template personalization? ...

(RECOMMENDED_COMPOUNDS)


[uncategorized] ~484-~484: Do not mix variants of the same word (‘personalization’ and ‘personalisation’) within a single text.
Context: ... Where is user name stored for template personalization? - Do we need to fetch user data fro...

(EN_WORD_COHERENCY)

VIBE-221-plan.md

[grammar] ~132-~132: Use a hyphen to join words.
Context: ...Retry Policy Decision: Retry once, 5 second delay Rationale: - Most trans...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~151-~151: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...for parallelism - GOV.UK Notify handles rate limiting - Better visibility into individual fai...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/VIBE-221/specification.md

[uncategorized] ~354-~354: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...Errors**: - Count by error type - Rate limiting occurrences - Invalid email addresse...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~396-~396: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... Client**: - Mock API responses - Rate limiting handling - Error scenarios 3. **Aud...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~525-~525: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... Mitigations ### Risk 1: GOV.UK Notify Rate Limiting Impact: High volume of notification...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/tickets/VIBE-221/specification.md

[style] ~7-~7: Try moving the adverb to make the sentence clearer.
Context: ...rigger-based mechanism in the CaTH back end to automatically send email notifications to subscribed users through Gov.Notify ...

(SPLIT_INFINITIVE)

.ai/plans/VIBE-221/plan.md

[style] ~475-~475: Consider using a different verb for a more formal wording.
Context: ...on flow continues normally 4. Debug and fix issues 5. Redeploy with fix ## Perform...

(FIX_RESOLVE)


[uncategorized] ~525-~525: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ses - Validate template parameters ### Rate Limiting - Respect Gov.Notify rate limits - Moni...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.18.1)
docs/VIBE-221/tasks.md

771-771: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


778-778: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


784-784: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


790-790: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


796-796: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


802-802: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


807-807: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


812-812: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/VIBE-221/plan.md

38-38: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/tickets/VIBE-221/tasks.md

106-106: Bare URL used

(MD034, no-bare-urls)

docs/tickets/VIBE-221/plan.md

41-41: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


156-156: Bare URL used

(MD034, no-bare-urls)


163-163: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


170-170: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


181-181: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


187-187: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


191-191: Bare URL used

(MD034, no-bare-urls)


541-541: Bare URL used

(MD034, no-bare-urls)

VIBE-221-plan.md

238-238: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/VIBE-221/specification.md

41-41: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


202-202: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


509-509: Bare URL used

(MD034, no-bare-urls)


650-650: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

VIBE-221-specification.md

49-49: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


60-60: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


66-66: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


70-70: Bare URL used

(MD034, no-bare-urls)


107-107: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


157-157: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


263-263: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

.ai/plans/VIBE-221/specification.md

38-38: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


60-60: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


116-116: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Comment thread libs/notification/src/notification-service.ts Outdated
Comment thread libs/notification/src/notification-service.ts Outdated
Comment thread libs/notification/types/notifications-node-client.d.ts Outdated
Comment thread libs/notifications/src/govnotify/govnotify-client.ts
Comment thread libs/notifications/src/govnotify/template-config.ts
Comment thread libs/notifications/types/notifications-node-client.d.ts Outdated
Comment thread libs/subscriptions/src/repository/queries.ts
- Set environment variables before module imports to ensure they're available
- Fixed vi.fn() mock to use function declaration instead of arrow function
- All 17 notifications tests now passing
- Pass Date object instead of ISO string for publicationDate
- Include locationName parameter (fetch from database)
- Use friendlyName instead of internal name for hearingListName
- Properly handle missing location case
- Fixed indentation and closing braces
- Added IF EXISTS to DROP CONSTRAINT statements
- Prevents migration failures when constraint doesn't exist
- Fixes GitHub Actions build error
- Removed 20251201115015_govnotifier_notification migration
- This migration created 'notification_log' table (incorrect)
- Keeping 20251201095418 which creates 'notification_audit_log' (correct)
- Prevents table name mismatch with schema definition
- Added IF NOT EXISTS to CREATE TABLE statement
- Added IF NOT EXISTS to all CREATE INDEX statements
- Wrapped ADD CONSTRAINT in DO blocks with existence checks
- Wrapped DROP DEFAULT in conditional check for existing default
- Migration can now be safely re-run without errors

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
libs/notifications/src/govnotify/govnotify-client.test.ts (1)

29-54: Expand test coverage to include error scenarios and retry logic.

The test suite currently only covers the happy path. Consider adding test cases for:

  • Email sending failures and error handling
  • Retry logic with backoff (based on the retryWithBackoff implementation)
  • Missing or invalid environment variables
  • Invalid email addresses or missing template parameters

This will help prevent regressions and ensure robust error handling.

libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)

111-122: Remove redundant comments that describe "what" rather than "why".

The comments on lines 111, 113, and 120 describe what the code does rather than why. Per the coding guidelines, comments should explain why something is done when the code itself is not self-explanatory. These comments can be safely removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f83f5e and 54f3093.

📒 Files selected for processing (3)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts (2 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notifications/src/govnotify/govnotify-client.test.ts
🧬 Code graph analysis (2)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (3)
libs/location/src/repository/queries.ts (1)
  • getLocationById (37-65)
libs/publication/src/index.ts (1)
  • mockListTypes (1-1)
libs/list-types/common/src/index.ts (1)
  • mockListTypes (2-2)
libs/notifications/src/govnotify/govnotify-client.test.ts (4)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)

Comment thread libs/admin-pages/src/pages/manual-upload-summary/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (12)
libs/api/src/blob-ingestion/repository/service.test.ts (1)

301-517: Refactor tests to avoid setTimeout pattern for async operations.

All notification-related tests use await new Promise((resolve) => setTimeout(resolve, 50)) to wait for fire-and-forget async operations. This pattern is problematic because:

  • It's non-deterministic: the async operation might not complete within 50ms on slower environments
  • It adds artificial delays that slow down test execution
  • It can cause flaky test failures

Consider refactoring the production code to expose the notification promise for testing, either through:

  • A test-mode flag that makes the operation awaitable
  • Dependency injection that allows tests to intercept and await the async flow
  • Using Vitest's vi.waitFor() or vi.waitUntil() with proper async conditions

Example using vi.waitFor():

-    // Wait for async notification processing
-    await new Promise((resolve) => setTimeout(resolve, 50));
+    // Wait for the mock to be called
+    await vi.waitFor(() => {
+      expect(sendPublicationNotifications).toHaveBeenCalled();
+    });

Note: This still requires the production code to complete quickly, but provides better error messages and can have configurable timeouts.

e2e-tests/utils/notification-helpers.ts (1)

35-55: Consider extracting common Prisma type casting.

Multiple functions use (prisma as any) to access models not typed in the shared client. While this works, it reduces type safety. If this pattern is used elsewhere, consider creating typed wrappers or extending the shared Prisma client to include these models.

e2e-tests/tests/manual-upload.spec.ts (2)

5-12: Unused import: getNotificationsByPublicationId.

This helper is imported but never used in the tests. The tests only verify the success panel is visible, not that notification records were created. Consider adding assertions using getNotificationsByPublicationId to verify the notification audit trail, or remove the unused import.


839-839: Fixed delay may cause test flakiness.

The setTimeout(resolve, 2000) delays assume notifications complete within 2 seconds. Under load or with slower environments, this could cause intermittent failures. Consider polling for notification records with a timeout instead:

// Example: Poll for notification completion
const maxWait = 10000;
const pollInterval = 500;
let elapsed = 0;
while (elapsed < maxWait) {
  const notifications = await getNotificationsByPublicationId(publicationId);
  if (notifications.length > 0) break;
  await new Promise(r => setTimeout(r, pollInterval));
  elapsed += pollInterval;
}

Also applies to: 883-883

libs/notifications/src/govnotify/govnotify-client.test.ts (1)

146-182: Verify exponential backoff test assertion.

The test checks that delays[0] equals 1000ms (default delay), but it only verifies the first retry delay. For true exponential backoff verification, consider testing with more retry attempts to confirm the delay increases (e.g., 1000, 2000, 4000ms). Currently this only confirms a delay was applied, not that it's exponential.

libs/notifications/src/notification/notification-service.test.ts (1)

300-332: Consider adding a test for partial audit log cleanup on exceptions.

When findExistingNotification throws, the test correctly expects failed: 1 in the results. However, if the exception occurs after createNotificationAuditLog succeeds (e.g., during email sending), the audit log may remain in "Pending" status. Consider adding a test case that verifies updateNotificationStatus is called with "Failed" when an exception occurs after audit log creation.

e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

78-78: Hardcoded delays are fragile for async notification processing.

Using fixed setTimeout delays (2000ms) to wait for notification processing is brittle. Under load or slower environments, tests may fail spuriously. Consider implementing a polling mechanism:

async function waitForNotifications(publicationId: string, expectedCount: number, timeoutMs = 10000) {
  const startTime = Date.now();
  while (Date.now() - startTime < timeoutMs) {
    const notifications = await getNotificationsByPublicationId(publicationId);
    if (notifications.length >= expectedCount && 
        notifications.every(n => n.status !== "Pending")) {
      return notifications;
    }
    await new Promise(r => setTimeout(r, 500));
  }
  throw new Error(`Timed out waiting for ${expectedCount} notifications`);
}

This same issue applies to lines 105, 134, 158, 176, and 198.

libs/notifications/prisma/schema.prisma (1)

17-17: Consider using an enum for status field.

Using String for status allows any value. Define a Prisma enum to restrict to valid statuses and improve type safety:

enum NotificationStatus {
  Pending
  Sent
  Failed
  Skipped
}

model NotificationAuditLog {
  // ...
  status NotificationStatus @default(Pending)
  // ...
}
libs/notifications/src/notification/notification-queries.ts (4)

3-20: Avoid duplicating the Prisma model shape in local interfaces

NotificationAuditLog (and to a lesser extent CreateNotificationData) mirror the Prisma model fields. This will drift if the schema changes (new/renamed columns, nullability tweaks). If @hmcts/postgres exposes the generated Prisma types, consider aliasing those here and deriving CreateNotificationData from them (e.g. Pick<...>), so the compiler catches schema changes.


22-33: Make status a well-defined domain type and avoid magic strings

Right now status is an untyped string with a hard‑coded "Pending" default. It would be safer to introduce a small status domain (string literal union or enum, plus STATUS_PENDING constant) and use nullish coalescing for the default, e.g. status ?? STATUS_PENDING, so callers and future refactors can’t silently pass invalid values.


66-70: Clarify uniqueness / determinism for govNotifyId lookups

If govNotifyId is guaranteed unique at the DB level, prefer findUnique over findFirst to encode that invariant and fail fast if it is ever violated. If it is not unique by design, consider adding an explicit orderBy here so the chosen record is deterministic (e.g. most recent by createdAt), and document that behaviour in the caller.


72-75: Align publication lookups with deterministic ordering / reuse existing helper

getNotificationsByPublicationId returns results without an orderBy, while the e2e helper at e2e-tests/utils/notification-helpers.ts orders by createdAt: "asc". For consistency and to avoid tests or callers depending on implicit DB ordering, consider adding the same orderBy here and, if practical, having the e2e helper call this function instead of duplicating the query.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54f3093 and 96271e3.

📒 Files selected for processing (18)
  • apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql (1 hunks)
  • e2e-tests/README-NOTIFICATIONS.md (1 hunks)
  • e2e-tests/run-with-credentials.js (1 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/tests/manual-upload.spec.ts (2 hunks)
  • e2e-tests/utils/api-auth-helpers.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (2 hunks)
  • libs/api/src/blob-ingestion/repository/service.test.ts (3 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
  • libs/notifications/src/notification/validation.ts (1 hunks)
  • libs/subscriptions/src/repository/queries.test.ts (2 hunks)
  • types/notifications-node-client.d.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • e2e-tests/README-NOTIFICATIONS.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/utils/api-auth-helpers.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • types/notifications-node-client.d.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/utils/api-auth-helpers.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • e2e-tests/run-with-credentials.js
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • types/notifications-node-client.d.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • libs/notifications/prisma/schema.prisma
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
🧠 Learnings (4)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't ignore TypeScript errors - fix or justify with comments.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't create types.ts files - colocate types with the appropriate code.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't duplicate dependencies - check root package.json first.

Applied to files:

  • types/notifications-node-client.d.ts
🧬 Code graph analysis (9)
libs/subscriptions/src/repository/queries.test.ts (1)
libs/subscriptions/src/repository/queries.ts (1)
  • findSubscriptionsByLocationId (14-23)
e2e-tests/tests/manual-upload.spec.ts (1)
e2e-tests/utils/notification-helpers.ts (4)
  • cleanupTestSubscriptions (84-92)
  • cleanupTestUsers (94-102)
  • createTestUser (19-33)
  • createTestSubscription (35-55)
libs/notifications/src/notification/notification-queries.ts (1)
e2e-tests/utils/notification-helpers.ts (1)
  • getNotificationsByPublicationId (57-62)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (3)
e2e-tests/utils/notification-helpers.ts (7)
  • cleanupTestNotifications (74-82)
  • cleanupTestSubscriptions (84-92)
  • cleanupTestUsers (94-102)
  • createTestUser (19-33)
  • createTestSubscription (35-55)
  • getNotificationsByPublicationId (57-62)
  • getGovNotifyEmail (64-72)
e2e-tests/utils/api-auth-helpers.ts (1)
  • getApiAuthToken (20-70)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (72-76)
libs/notifications/src/notification/notification-service.ts (5)
libs/notifications/src/notification/validation.ts (3)
  • PublicationEvent (13-19)
  • validatePublicationEvent (26-53)
  • isValidEmail (6-11)
libs/notifications/src/notification/subscription-queries.ts (2)
  • findActiveSubscriptionsByLocation (14-31)
  • SubscriptionWithUser (3-12)
libs/notifications/src/notification/notification-queries.ts (3)
  • findExistingNotification (53-64)
  • createNotificationAuditLog (22-33)
  • updateNotificationStatus (35-51)
libs/notifications/src/govnotify/template-config.ts (1)
  • buildTemplateParameters (41-54)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (18-29)
e2e-tests/utils/notification-helpers.ts (2)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (72-76)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-30)
libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (7)
libs/admin-pages/src/manual-upload/storage.ts (1)
  • getManualUpload (46-68)
libs/admin-pages/src/manual-upload/file-storage.ts (1)
  • saveUploadedFile (12-23)
libs/publication/src/repository/queries.ts (1)
  • createArtefact (4-52)
libs/publication/src/index.ts (1)
  • createArtefact (6-6)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)
  • POST (212-212)
libs/api/src/routes/v1/publication.ts (1)
  • POST (26-72)
libs/location/src/repository/queries.ts (1)
  • getLocationById (37-65)
libs/notifications/src/govnotify/govnotify-client.test.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-30)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (18-29)
libs/api/src/blob-ingestion/repository/service.test.ts (3)
libs/api/src/blob-ingestion/validation.ts (1)
  • validateBlobRequest (9-158)
libs/api/src/blob-ingestion/repository/service.ts (1)
  • processBlobIngestion (17-128)
libs/api/src/blob-ingestion/repository/queries.ts (1)
  • createIngestionLog (4-16)
🔇 Additional comments (25)
libs/api/src/blob-ingestion/repository/service.test.ts (4)

6-40: LGTM!

The mock additions follow the existing pattern and provide appropriate test doubles for the new dependencies.


43-48: LGTM!

Import updates correctly reference the newly added mocks and mockListTypes.


220-299: LGTM!

These tests provide thorough coverage of edge cases including missing fields, system errors, and non-Error exceptions with appropriate fallback behaviors.


301-336: LGTM!

The notification test coverage is comprehensive, including:

  • Successful notification flow with proper mock data
  • Skipping notifications when location is not matched
  • Graceful error handling that doesn't fail ingestion
  • Edge cases for invalid/missing location and list type data
  • Detailed logging verification for both success and error paths

The test logic and assertions are appropriate, with proper setup and teardown of console spies.

Also applies to: 338-354, 356-390, 392-414, 416-438, 440-465, 467-517

libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (4)

566-602: Good test coverage for notification flow.

The test properly verifies that sendPublicationNotifications is called with the expected payload after a successful upload. The assertions on publicationId, locationId, locationName, hearingListName, and publicationDate provide comprehensive validation.


604-635: Resilience testing for missing location is well-implemented.

The test correctly verifies that the upload succeeds even when getLocationById returns null, and that notifications are skipped with an appropriate warning log. The console spy cleanup with mockRestore() is properly handled.


637-675: Good fire-and-forget resilience test.

This test validates that notification failures don't block the upload flow, which aligns with the async notification architecture described in the AI summary. The error logging verification ensures observability is maintained.


740-856: Comprehensive isFlatFile logic coverage.

The tests cover JSON files (isFlatFile: false), non-JSON files (isFlatFile: true), and the edge case of missing fileName (defaults to true). This matches the expected behavior for flat file determination.

e2e-tests/run-with-credentials.js (1)

23-25: LGTM - Azure credentials added for API E2E tests.

The new secret mappings align with the api-auth-helpers.ts requirements and follow the existing naming conventions. Good that these credentials are not logged (unlike SSO/CFT emails), maintaining security best practices.

libs/subscriptions/src/repository/queries.test.ts (1)

78-120: Well-structured tests for new location-based subscription query.

The tests follow the established patterns in the file and properly verify that findSubscriptionsByLocationId passes the correct parameters to Prisma (where.locationId, orderBy.dateAdded: "desc"). Coverage for both the happy path and empty result case is appropriate.

apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql (1)

1-15: Well-structured idempotent migration.

The migration properly uses IF NOT EXISTS checks for both the column addition and index creation, ensuring it can be safely re-run. The gov_notify_id column and index support the notification audit functionality introduced in this PR.

e2e-tests/tests/manual-upload.spec.ts (1)

795-801: Good cleanup order respects foreign key constraints.

The cleanup correctly deletes subscriptions before users, which avoids FK constraint violations. The tracking pattern with testData arrays ensures reliable cleanup even if tests fail mid-execution.

libs/notifications/src/govnotify/govnotify-client.test.ts (3)

29-54: Comprehensive test for successful email sending.

The test properly verifies the template ID, email address, and personalisation parameters are passed correctly to the NotifyClient. The assertion structure clearly validates the expected behavior.


77-101: Good retry behavior coverage.

Testing that the client retries on first failure and succeeds on the second attempt validates the retry logic works correctly with NOTIFICATION_RETRY_ATTEMPTS=1 (initial + 1 retry = 2 calls).


29-30: Dynamic imports may cause module caching issues.

Using await import("./govnotify-client.js") in each test could lead to the module being cached after the first import, meaning subsequent tests might not get fresh module instances. If tests start failing intermittently, consider using vi.resetModules() in beforeEach to ensure each test gets a fresh module instance.

Also applies to: 56-57, 77-78, 103-104, 125-126, 146-147

e2e-tests/utils/api-auth-helpers.ts (2)

20-70: Well-implemented token caching with proper error handling.

The implementation correctly:

  • Caches tokens with a 5-minute safety buffer before expiry
  • Provides clear error messages when credentials are missing
  • Handles both HTTP errors and exceptions appropriately
  • Avoids logging sensitive data (tokens/secrets)

The client credentials flow with /.default scope is the correct pattern for Azure AD service-to-service authentication.


12-13: Module-level state is acceptable for E2E test helpers.

While module-level mutable state (cachedToken, tokenExpiry) is generally discouraged, it's appropriate here for E2E test utilities where token reuse across tests improves performance. The clearCachedToken() function provides a way to reset state when needed.

libs/notifications/src/notification/notification-service.test.ts (2)

1-79: Well-structured test setup and coverage.

The mock setup and first test case correctly verify the notification flow for subscribed users. The use of vi.mock at the module level followed by vi.mocked to configure return values is appropriate for Vitest.


182-204: LGTM - validation error tests are correctly structured.

The toThrow() assertions use substring matching, which will correctly match the actual error messages from the implementation (e.g., "Invalid publication event: Publication ID is required, ..." will match "Invalid publication event").

libs/notifications/src/notification/notification-service.ts (2)

21-86: Good use of Promise.allSettled for concurrent notification processing.

The implementation correctly uses Promise.allSettled to handle per-user notifications independently, preventing a single failure from affecting other users. The aggregation logic properly handles both fulfilled and rejected promises.


171-174: LGTM - buildUserName helper is concise and handles edge cases.

Correctly filters out null/undefined values and provides a sensible fallback.

e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (2)

39-46: Good use of test.skip for conditional test execution.

The beforeAll hook appropriately skips tests when Azure AD OAuth tokens aren't available, with a clear explanation message directing users to alternative test suites.


48-56: Clean test data cleanup in afterEach.

The cleanup order (notifications → subscriptions → users) respects foreign key constraints. Good pattern for test isolation.

libs/notifications/prisma/schema.prisma (2)

11-29: Schema follows naming conventions correctly.

Good use of @map and @@map to maintain snake_case in the database while using camelCase in the Prisma client. The indexes on publicationId, status, and govNotifyId support the query patterns in notification-queries.ts.


22-22: No action needed. The Subscription model is properly defined in libs/subscriptions/prisma/schema.prisma and the relation reference on line 22 is valid. This multi-file Prisma schema setup allows cross-module model references, which is correctly configured in this codebase.

Comment thread e2e-tests/tests/api/blob-ingestion-notifications.spec.ts Outdated
Comment thread e2e-tests/utils/notification-helpers.ts Outdated
Comment thread e2e-tests/utils/notification-helpers.ts Outdated
Comment thread libs/notifications/src/notification/notification-service.ts
Comment thread libs/notifications/src/notification/notification-service.ts
Comment thread libs/notifications/src/notification/notification-service.ts
Comment thread types/notifications-node-client.d.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
e2e-tests/utils/notification-helpers.ts (1)

65-73: Verify getNotificationById type definition exists.

The code calls notifyClient.getNotificationById(notificationId) which may not be properly typed in types/notifications-node-client.d.ts. If TypeScript compilation succeeds, the type definition exists. Otherwise, you'll need to extend the type definition.

#!/bin/bash
# Check if getNotificationById is defined in the type definition
fd -t f "notifications-node-client.d.ts" | xargs rg "getNotificationById"
libs/notifications/src/notification/notification-service.ts (3)

84-98: Redundant double-write persists: audit log created then immediately updated with same status.

The code creates a notification audit log with status "Skipped" (line 89), then immediately calls updateNotificationStatus with the same "Skipped" status and error message (line 92). This is an unnecessary database round-trip.

Per previous review feedback, pass the error message during creation:

     if (!subscription.user.email) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "No email address"
       });

-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "No email address");
-
       return {
         status: "skipped",
         error: `User ${subscription.userId}: No email address`
       };
     }

This requires adding an optional errorMessage field to the CreateNotificationData interface in notification-queries.ts.


100-114: Same double-write issue for invalid email case.

Same pattern as the no-email case above - creates audit log then immediately updates it with the same status.

     if (!isValidEmail(subscription.user.email)) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "Invalid email format"
       });

-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "Invalid email format");
-
       return {
         status: "skipped",
         error: `User ${subscription.userId}: Invalid email format`
       };
     }

148-155: Exception handling doesn't update audit log status on failure.

If createNotificationAuditLog succeeds (line 116) but a subsequent exception occurs before updateNotificationStatus, the audit log remains in "Pending" status indefinitely. This leaves orphaned records in the database.

Refactor to ensure the audit log is updated even on exceptions:

+    let notification;
     try {
-      const notification = await createNotificationAuditLog({
+      notification = await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
         status: "Pending"
       });
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : String(error);
+      return {
+        status: "failed",
+        error: `User ${subscription.userId}: ${errorMessage}`
+      };
+    }

+    try {
       const userName = buildUserName(subscription.user.firstName, subscription.user.surname);
       const templateParameters = buildTemplateParameters({
         userName,
         hearingListName: event.hearingListName,
         publicationDate: event.publicationDate,
         locationName: event.locationName
       });

       const emailResult = await sendEmail({
         emailAddress: subscription.user.email,
         templateParameters
       });

       if (emailResult.success) {
         await updateNotificationStatus(notification.notificationId, "Sent", new Date(), undefined, emailResult.notificationId);
         return {
           status: "sent"
         };
       }

       await updateNotificationStatus(notification.notificationId, "Failed", undefined, emailResult.error);
       return {
         status: "failed",
         error: `User ${subscription.userId}: ${emailResult.error}`
       };
     } catch (error) {
       const errorMessage = error instanceof Error ? error.message : String(error);
+      await updateNotificationStatus(notification.notificationId, "Failed", undefined, errorMessage);
       return {
         status: "failed",
         error: `User ${subscription.userId}: ${errorMessage}`
       };
     }
🧹 Nitpick comments (4)
libs/notifications/src/govnotify/notifications-node-client.d.ts (1)

4-4: Broaden sendEmail options type to cover full GOV.UK Notify API.

Right now the options parameter only exposes personalisation, but Notify also supports reference and emailReplyToId on this options object.(npmjs.com) This is not wrong for current usage but will block you from typing those fields later.

Consider expanding the signature to more closely match the upstream API:

-    sendEmail(templateId: string, emailAddress: string, options?: { personalisation?: Record<string, string> }): Promise<any>;
+    sendEmail(
+      templateId: string,
+      emailAddress: string,
+      options?: {
+        personalisation?: Record<string, string>;
+        reference?: string;
+        emailReplyToId?: string;
+      }
+    ): Promise<any>;

Please double‑check against the exact notifications-node-client version you depend on to ensure this matches its documented options shape.

libs/notifications/src/govnotify/template-config.ts (1)

5-10: Variable name doesn't match the environment variable source.

The constant GOVUK_NOTIFY_TEMPLATE_ID is sourced from GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION (line 2), but the error message correctly references the full name. Consider renaming the constant to match the environment variable for clarity:

-const GOVUK_NOTIFY_TEMPLATE_ID = process.env.GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION || "";
+const GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION = process.env.GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION || "";

 export function getTemplateId(): string {
-  if (!GOVUK_NOTIFY_TEMPLATE_ID) {
+  if (!GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION) {
     throw new Error("GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION environment variable is not set");
   }
-  return GOVUK_NOTIFY_TEMPLATE_ID;
+  return GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION;
 }
libs/notifications/src/notification/notification-queries.test.ts (2)

18-41: Add assertion to verify the create method was called with correct arguments.

The test validates the returned result but doesn't verify that prisma.notificationAuditLog.create was called with the expected data. This would catch regressions if the function incorrectly transforms the input.

   const result = await createNotificationAuditLog({
     subscriptionId: "sub-1",
     userId: "user-1",
     publicationId: "pub-1"
   });

+  expect(prisma.notificationAuditLog.create).toHaveBeenCalledWith({
+    data: {
+      subscriptionId: "sub-1",
+      userId: "user-1",
+      publicationId: "pub-1",
+      status: "Pending"
+    }
+  });
   expect(result.notificationId).toBe("notif-1");
   expect(result.status).toBe("Pending");

43-57: Add test case with all optional parameters.

The current test only exercises sentAt. Add a test case that includes errorMessage and govNotifyId to ensure all optional parameters are correctly passed through:

it("should update notification status with all optional parameters", async () => {
  const { prisma } = await import("@hmcts/postgres");
  const sentAt = new Date();

  await updateNotificationStatus("notif-1", "Failed", sentAt, "Send failed", "gov-notify-123");

  expect(prisma.notificationAuditLog.update).toHaveBeenCalledWith({
    where: { notificationId: "notif-1" },
    data: {
      status: "Failed",
      sentAt,
      errorMessage: "Send failed",
      govNotifyId: "gov-notify-123"
    }
  });
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96271e3 and b7dca26.

📒 Files selected for processing (15)
  • apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql (1 hunks)
  • docs/tickets/VIBE-221/plan.md (1 hunks)
  • e2e-tests/README-NOTIFICATIONS.md (1 hunks)
  • e2e-tests/run-with-credentials.js (1 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/govnotify/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/src/govnotify/template-config.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • libs/notifications/src/govnotify/govnotify-client.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/run-with-credentials.js
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/README-NOTIFICATIONS.md
  • libs/notifications/src/govnotify/govnotify-client.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notifications/src/govnotify/notifications-node-client.d.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/notifications/src/govnotify/template-config.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notifications/src/govnotify/notifications-node-client.d.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/notifications/src/govnotify/template-config.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notifications/src/notification/notification-queries.test.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • libs/notifications/prisma/schema.prisma
🧠 Learnings (1)
📚 Learning: 2025-11-27T09:48:13.010Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 136
File: libs/api/src/blob-ingestion/validation.ts:156-163
Timestamp: 2025-11-27T09:48:13.010Z
Learning: In libs/api/src/blob-ingestion/validation.ts, the permissive date validation in isValidISODate and isValidISODateTime functions is expected behavior and should not be flagged for stricter validation.

Applied to files:

  • libs/notifications/src/govnotify/template-config.ts
🧬 Code graph analysis (2)
libs/notifications/src/notification/notification-queries.test.ts (1)
libs/notifications/src/notification/notification-queries.ts (2)
  • createNotificationAuditLog (22-33)
  • updateNotificationStatus (35-51)
libs/notifications/src/notification/notification-queries.ts (1)
e2e-tests/utils/notification-helpers.ts (1)
  • getNotificationsByPublicationId (58-63)
🪛 LanguageTool
docs/tickets/VIBE-221/plan.md

[style] ~484-~484: It’s more common nowadays to write this noun as one word.
Context: ... table with email field?) - Where is user name stored for template personalization? ...

(RECOMMENDED_COMPOUNDS)


[uncategorized] ~484-~484: Do not mix variants of the same word (‘personalization’ and ‘personalisation’) within a single text.
Context: ... Where is user name stored for template personalization? - Do we need to fetch user data fro...

(EN_WORD_COHERENCY)

🪛 markdownlint-cli2 (0.18.1)
docs/tickets/VIBE-221/plan.md

41-41: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


156-156: Bare URL used

(MD034, no-bare-urls)


163-163: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


170-170: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


181-181: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


187-187: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


191-191: Bare URL used

(MD034, no-bare-urls)


541-541: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests
🔇 Additional comments (24)
apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql (1)

1-2: Add documentation explaining the business rationale for removing the unique constraint.

The removal of the unique index on (user_id, publication_id) lacks context. While the current notification service creates one notification per subscription (and subscriptions enforce uniqueness on (userId, locationId)), the constraint removal suggests either a bug fix for existing duplicates or a future design change.

Clarify:

  • Why was the constraint needed initially, then removed one day later?
  • Are there code paths outside the subscription model that create notifications?
  • Does existing data contain duplicates that this migration needed to accommodate?
  • Is the vague commit message "fix bug" referring to a specific issue?

Add a comment in the migration explaining the rationale to prevent future confusion.

libs/notifications/src/govnotify/notifications-node-client.d.ts (1)

1-7: Remove duplicate and incomplete ambient module declaration; update types/notifications-node-client.d.ts instead.

Two conflicting declare module declarations exist for "notifications-node-client":

  1. types/notifications-node-client.d.ts — comprehensive but missing getNotificationById (used in e2e-tests)
  2. libs/notifications/src/govnotify/notifications-node-client.d.ts — incomplete and redundant

Delete libs/notifications/src/govnotify/notifications-node-client.d.ts and add getNotificationById to the declaration in types/notifications-node-client.d.ts to resolve the type inconsistency. The types/ location is the proper place for ambient module declarations (it's in typeRoots), and having both creates conflicting type information across the codebase.

⛔ Skipped due to learnings
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't create types.ts files - colocate types with the appropriate code.
libs/notifications/src/govnotify/template-config.ts (4)

1-3: LGTM!

The constants use appropriate defaults, with empty strings for required credentials that are validated by getTemplateId() and getApiKey(), and a production URL fallback for CATH_SERVICE_URL.


12-17: LGTM!

Proper validation with clear error message.


19-21: LGTM!

Returns the service URL with a sensible production default.


32-39: Confirm if template parameter keys are constrained by Gov.UK Notify template requirements.

The interface uses mixed naming conventions (ListType in PascalCase, content_date and start_page_link in snake_case, locations in camelCase). These keys are passed directly as personalisation to the Gov.UK Notify API's sendEmail() method, suggesting they may be required to match exact template placeholder names.

If these exact names are dictated by the Gov.UK Notify template, the mixed casing is acceptable and should be documented. Otherwise, standardize to camelCase per coding guidelines.

libs/notifications/src/notification/notification-queries.test.ts (1)

4-11: LGTM!

The mock properly isolates the Prisma client for unit testing.

libs/notifications/src/notification/notification-service.ts (3)

20-80: LGTM!

The orchestration logic properly validates input, handles partial failures with Promise.allSettled, and aggregates results correctly. The error handling for both fulfilled and rejected promises is comprehensive.


116-147: LGTM!

The email sending logic correctly creates an audit log with "Pending" status, attempts the send, and updates to the final status ("Sent" or "Failed") based on the result. The status transitions are appropriate.


157-160: LGTM!

The helper correctly handles null values and provides a sensible fallback.

libs/notifications/prisma/schema.prisma (2)

1-9: LGTM!

Standard Prisma configuration with shared client output.


11-27: Unique constraint was intentionally removed—do not re-add without clarification.

The schema currently lacks the @@unique([userId, publicationId]) constraint, but this is not an oversight. The constraint was created in migration 20251201095418_add_notification_audit_log and then explicitly dropped in migration 20251202095746_remove_notification_unique_constraint.

The plan (docs/tickets/VIBE-221/plan.md lines 92-93) requires this constraint for deduplication, but the codebase has since removed it. Before re-adding the constraint to the schema, clarify why it was removed and whether that decision should be reversed or the plan should be updated.

docs/tickets/VIBE-221/plan.md (1)

1-552: Planning document - no code review required.

This is a technical planning document. The static analysis hints are markdown formatting suggestions (e.g., adding language identifiers to code blocks, handling bare URLs) that don't impact functionality. The document provides valuable context for the implementation.

libs/notifications/src/notification/notification-queries.ts (4)

22-33: LGTM with enhancement suggested above.

The function correctly creates an audit log entry with a sensible default status. See the previous comment about adding errorMessage support.


35-51: LGTM!

The function correctly updates the notification status with all optional fields.


53-57: LGTM!

Appropriate query with null handling for not-found cases.


59-63: LGTM!

Appropriate query for retrieving all notifications for a publication.

e2e-tests/utils/notification-helpers.ts (7)

5-17: LGTM!

Interfaces follow camelCase naming convention as per coding guidelines.


19-35: LGTM!

Test user creation logic is correct with proper typing and sensible defaults.


58-63: LGTM!

Query logic is correct and consistent with the main implementation in notification-queries.ts.


75-84: LGTM!

Defensive filtering and early return prevent errors with empty arrays. Good practice.


86-94: LGTM!

Consistent cleanup pattern with appropriate guards.


96-104: LGTM!

Proper typing with no cast needed since user is in the main Prisma client.


37-56: The dateAdded field has a database default and does not require an explicit value.

The Subscription model in libs/subscriptions/prisma/schema.prisma defines dateAdded with @default(now()), so the field will be automatically populated by the database when the record is created. The current implementation at lines 40-46 is correct. The (prisma as any) cast is acceptable for test helpers.

Comment thread libs/notifications/src/notification/notification-queries.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d773e0 and af517ec.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • e2e-tests/tests/add-jurisdiction.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use camelCase for TypeScript variables: userId, caseDetails, documentId
Use PascalCase for classes and interfaces: UserService, CaseRepository. NO I prefix.
Use kebab-case for file and directory names: user-service.ts, case-management/
Use boolean variable names with is/has/can prefix: isActive, hasAccess, canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add .js extension to relative imports in ES modules (e.g., import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; no any without justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source code using *.test.ts or *.spec.ts naming pattern

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

e2e-tests/**/*.spec.ts: E2E tests must be located in e2e-tests/ directory with *.spec.ts naming pattern
Tag nightly-only E2E tests with @nightly in the test title
E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()
Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests
Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey
Do not test visual styling (fonts, colors, margins, padding) in E2E tests

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use CommonJS - ES modules only with import/export syntax

Files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
🧠 Learnings (5)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey

Applied to files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests

Applied to files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Do not test visual styling (fonts, colors, margins, padding) in E2E tests

Applied to files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests must be located in `e2e-tests/` directory with `*.spec.ts` naming pattern

Applied to files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()

Applied to files:

  • e2e-tests/tests/add-jurisdiction.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Changed Packages
  • GitHub Check: E2E Tests

Comment thread e2e-tests/tests/add-jurisdiction.spec.ts
ashwini-mv and others added 2 commits December 18, 2025 18:51
Replace click() with keyboard.press('Enter') to properly test keyboard-
driven form submission. The test now genuinely verifies that users can
submit the form using only the keyboard, matching the test's comment
"Submit with keyboard".

Changes:
- Focus the save button
- Verify button is focused (accessibility check)
- Press 'Enter' key to submit (actual keyboard interaction)
- Assert navigation to success page

This ensures the test validates real keyboard accessibility rather than
simulating it with a mouse click.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@junaidiqbalmoj
junaidiqbalmoj merged commit 7fe732f into master Dec 23, 2025
9 checks passed
@junaidiqbalmoj
junaidiqbalmoj deleted the feature/VIBE-221-subscription-fulfilment-email branch December 24, 2025 14:49
@coderabbitai coderabbitai Bot mentioned this pull request Jan 22, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants