Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/terraform/components/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ No requirements.
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-kms.zip | n/a |
| <a name="module_letter_status_update"></a> [letter\_status\_update](#module\_letter\_status\_update) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
| <a name="module_letter_status_updates_queue"></a> [letter\_status\_updates\_queue](#module\_letter\_status\_updates\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
| <a name="module_letter_stream_forwarder"></a> [letter\_stream\_forwarder](#module\_letter\_stream\_forwarder) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
| <a name="module_letter_updates_transformer"></a> [letter\_updates\_transformer](#module\_letter\_updates\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
| <a name="module_logging_bucket"></a> [logging\_bucket](#module\_logging\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
| <a name="module_patch_letter"></a> [patch\_letter](#module\_patch\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/terraform/components/api/ddb_table_letters.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
resource "aws_dynamodb_table" "letters" {
name = "${local.csi}-letters"
billing_mode = "PAY_PER_REQUEST"
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
Comment thread
stevebux marked this conversation as resolved.
Outdated

hash_key = "supplierId"
range_key = "id"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
resource "aws_lambda_event_source_mapping" "letter_stream_forwarder_dynamodb" {
event_source_arn = aws_dynamodb_table.letters.stream_arn
function_name = module.letter_stream_forwarder.function_arn
starting_position = "LATEST"
Comment thread
m-houston marked this conversation as resolved.
Outdated
batch_size = 10
maximum_batching_window_in_seconds = 1
Comment thread
stevebux marked this conversation as resolved.
Outdated

depends_on = [
module.letter_stream_forwarder # ensures stream forwarder exists
]
}

resource "aws_lambda_event_source_mapping" "letter_updates_transformer_kinesis" {
event_source_arn = aws_kinesis_stream.letter_change_stream.arn
function_name = module.letter_updates_transformer.function_arn
starting_position = "LATEST"
batch_size = 10
maximum_batching_window_in_seconds = 1

depends_on = [
module.letter_updates_transformer # ensures updates transformer exists
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "aws_kinesis_stream" "letter_change_stream" {
name = "${local.csi}-letter-change-stream"
shard_count = 1
retention_period = 24
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module "letter_stream_forwarder" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip"

function_name = "letter-stream-forwarder"
description = "Kinesis stream forwarder for DDB letter status updates"

aws_account_id = var.aws_account_id
component = var.component
environment = var.environment
project = var.project
region = var.region
group = var.group

log_retention_in_days = var.log_retention_in_days
kms_key_arn = module.kms.key_arn

iam_policy_document = {
body = data.aws_iam_policy_document.letter_stream_forwarder_lambda.json
}

function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
function_code_base_path = local.aws_lambda_functions_dir_path
function_code_dir = "letter-stream-forwarder/dist"
function_include_common = true
handler_function_name = "handler"
runtime = "nodejs22.x"
memory = 128
timeout = 5
log_level = var.log_level

force_lambda_code_deploy = var.force_lambda_code_deploy
enable_lambda_insights = false

send_to_firehose = true
log_destination_arn = local.destination_arn
log_subscription_role_arn = local.acct.log_subscription_role_arn

lambda_env_vars = merge(local.common_lambda_env_vars, {
LETTER_CHANGE_STREAM_ARN = "${aws_kinesis_stream.letter_change_stream.arn}"
})
}

data "aws_iam_policy_document" "letter_stream_forwarder_lambda" {

statement {
sid = "AllowDynamoDBStream"
effect = "Allow"

actions = [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:DescribeStream",
"dynamodb:ListStreams",
]

resources = [
"${aws_dynamodb_table.letters.arn}/stream/*"
]
}

statement {
sid = "AllowKinesisPut"
effect = "Allow"

actions = [
"kinesis:DescribeStream",
"kinesis:PutRecord",
]

resources = [
aws_kinesis_stream.letter_change_stream.arn
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,39 @@ module "letter_updates_transformer" {
log_subscription_role_arn = local.acct.log_subscription_role_arn

lambda_env_vars = merge(local.common_lambda_env_vars, {
EVENTPUB_SNS_TOPIC_ARN = module.eventpub.sns_topic.arn
EVENTPUB_SNS_TOPIC_ARN = "${module.eventpub.sns_topic.arn}"
})
}

data "aws_iam_policy_document" "letter_updates_transformer_lambda" {
statement {
sid = "KMSPermissions"
sid = "AllowSNSPublish"
effect = "Allow"

actions = [
"kms:Decrypt",
"kms:GenerateDataKey",
"sns:Publish"
]

resources = [
module.kms.key_arn,
module.eventpub.sns_topic.arn
]
}

statement {
sid = "AllowSNSPublish"
sid = "AllowKinesisGet"
effect = "Allow"

actions = [
"sns:Publish"
"kinesis:GetRecords",
"kinesis:GetShardIterator",
"kinesis:DescribeStream",
"kinesis:DescribeStreamSummary",
"kinesis:ListShards",
"kinesis:ListStreams",
]

resources = [
module.eventpub.sns_topic.arn
aws_kinesis_stream.letter_change_stream.arn
]
}
}
2 changes: 1 addition & 1 deletion internal/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@
"typecheck": "tsc --noEmit"
},
"types": "dist/index.d.ts",
"version": "1.0.3"
"version": "1.0.4"
}
9 changes: 9 additions & 0 deletions internal/events/src/events/__tests__/event-envelope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ describe("EventEnvelope schema validation", () => {
const baseValidEnvelope: Envelope = {
dataschema:
"https://notify.nhs.uk/cloudevents/schemas/supplier-api/order.READ.1.0.0.schema.json",
dataschemaversion: "1.0.0",
specversion: "1.0",
id: "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111",
source: "/data-plane/supplier-api/ordering",
subject: "order/769acdd4",
type: "uk.nhs.notify.supplier-api.order.READ.v1",
plane: "data-plane",
Comment thread
stevebux marked this conversation as resolved.
Outdated
time: "2025-10-01T10:15:30.000Z",
datacontenttype: "application/json",
data: {
"notify-payload": {
"notify-data": { nhsNumber: "9434765919" },
Expand Down Expand Up @@ -241,10 +244,13 @@ describe("EventEnvelope schema validation", () => {
specversion: "1.0" as const,
id: "6f1c2a53-3d54-4a0a-9a0b-0e02b2c3d479",
type: "uk.nhs.notify.supplier-api.letter.CREATED.v1" as const,
plane: "data-plane",
dataschema:
"https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.CREATED.1.0.0.schema.json",
dataschemaversion: "1.0.0",
source: "/data-plane/supplier-api/letters",
time: "2025-10-01T10:15:30.000Z",
datacontenttype: "application/json",
data: { status: "CREATED" },
traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
recordedtime: "2025-10-01T10:15:30.250Z",
Expand Down Expand Up @@ -294,11 +300,14 @@ describe("EventEnvelope schema validation", () => {
specversion: "1.0" as const,
id: "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111",
type: "uk.nhs.notify.supplier-api.order.READ.v1" as const,
plane: "data-plane",
dataschema:
"https://notify.nhs.uk/cloudevents/schemas/supplier-api/order.READ.1.0.0.schema.json",
dataschemaversion: "1.0.0",
source: "/data-plane/supplier-api/ordering",
subject: "order/769acdd4",
time: "2025-10-01T10:15:30.000Z",
datacontenttype: "application/json",
data: { status: "READ" },
traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
recordedtime: "2025-10-01T10:15:30.250Z",
Expand Down
2 changes: 2 additions & 0 deletions internal/events/src/events/__tests__/mi-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ describe("MI event validations", () => {
expect(event).toEqual(
expect.objectContaining({
type: "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1",
plane: "data-plane",
specversion: "1.0",
source: "/data-plane/supplier-api/prod/submit-mi",
id: "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
time: "2025-11-16T10:30:00.000Z",
datacontenttype: "application/json",
dataschema:
"https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
dataschemaversion: "1.0.0",
Comment thread
m-houston marked this conversation as resolved.
Outdated
subject: "mi/mi-test-001",
data: expect.objectContaining({
id: "mi-test-001",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.0.1.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
"plane": "data-plane",
Comment thread
stevebux marked this conversation as resolved.
Outdated
"recordedtime": "2025-08-28T08:45:00.000Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
"plane": "data-plane",
"recordedtime": "2025-08-28T08:45:00.000Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
"plane": "data-plane",
"recordedtime": "2025-08-28T08:45:00.000Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.FORWARDED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
"plane": "data-plane",
"recordedtime": "2025-08-28T08:45:00.000Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.RETURNED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
"plane": "data-plane",
"recordedtime": "2025-08-28T08:45:00.000Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "9a3d4c55-5f76-6c2c-b789-2f1cf4e5e333",
"plane": "data-plane",
"recordedtime": "2025-11-16T15:00:00.250Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.2.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
"plane": "data-plane",
"recordedtime": "2025-11-16T10:30:00.250Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
"plane": "data-plane",
"recordedtime": "2025-11-16T10:30:00.250Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
},
"datacontenttype": "application/json",
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
"dataschemaversion": "1.0.0",
"id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
"plane": "data-plane",
"recordedtime": "2025-11-16T10:30:00.250Z",
"severitynumber": 2,
"severitytext": "INFO",
Expand Down
19 changes: 17 additions & 2 deletions internal/events/src/events/event-envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export function EventEnvelope<TData extends z.ZodTypeAny>(
examples: typeStrings,
}),

plane: z.literal("data-plane").meta({
title: "plane",
description: "Fixed as data-plane",
examples: ["data-plane"],
}),
Comment thread
m-houston marked this conversation as resolved.
Outdated

dataschema: z
.string()
.regex(
Expand All @@ -64,6 +70,15 @@ export function EventEnvelope<TData extends z.ZodTypeAny>(
examples: schemaExamples,
}),

dataschemaversion: z
.string()
.regex(/^1.\d+.\d+$/)
.meta({
title: "Data Schema URI",
description: `Version of the schema that describes the event data\n\nMust match the version in dataschema`,
examples:["1.0.0"],
}),

source: z
.string()
.regex(/^\/data-plane\/supplier-api(?:\/.*)?$/)
Expand Down Expand Up @@ -94,13 +109,13 @@ export function EventEnvelope<TData extends z.ZodTypeAny>(
examples: ["2025-10-01T10:15:30.000Z"],
}),

datacontenttype: z.optional(
datacontenttype:
z.literal("application/json").meta({
title: "Data Content Type",
description:
"Media type for the data field (fixed to application/json).",
examples: ["application/json"],
}),
},
),

traceparent: z
Expand Down
1 change: 1 addition & 0 deletions lambdas/letter-stream-forwarder/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
4 changes: 4 additions & 0 deletions lambdas/letter-stream-forwarder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
coverage
node_modules
dist
.reports
Loading
Loading