diff --git a/infrastructure/terraform/components/sbx/README.md b/infrastructure/terraform/components/sbx/README.md index 216eae80c..78679a95e 100644 --- a/infrastructure/terraform/components/sbx/README.md +++ b/infrastructure/terraform/components/sbx/README.md @@ -42,6 +42,7 @@ | [download\_bucket\_name](#output\_download\_bucket\_name) | n/a | | [events\_sns\_topic\_arn](#output\_events\_sns\_topic\_arn) | n/a | | [internal\_bucket\_name](#output\_internal\_bucket\_name) | n/a | +| [proof\_requests\_table\_name](#output\_proof\_requests\_table\_name) | n/a | | [quarantine\_bucket\_name](#output\_quarantine\_bucket\_name) | n/a | | [request\_proof\_queue\_url](#output\_request\_proof\_queue\_url) | n/a | | [routing\_config\_table\_name](#output\_routing\_config\_table\_name) | n/a | diff --git a/infrastructure/terraform/components/sbx/outputs.tf b/infrastructure/terraform/components/sbx/outputs.tf index cab82d420..8f6710538 100644 --- a/infrastructure/terraform/components/sbx/outputs.tf +++ b/infrastructure/terraform/components/sbx/outputs.tf @@ -73,3 +73,7 @@ output "routing_config_table_name" { output "events_sns_topic_arn" { value = module.eventpub.sns_topic.arn } + +output "proof_requests_table_name" { + value = module.backend_api.proof_requests_table_name +} diff --git a/infrastructure/terraform/modules/backend-api/README.md b/infrastructure/terraform/modules/backend-api/README.md index b3ffa7f60..e4caa4208 100644 --- a/infrastructure/terraform/modules/backend-api/README.md +++ b/infrastructure/terraform/modules/backend-api/README.md @@ -68,6 +68,7 @@ No requirements. | [s3bucket\_internal](#module\_s3bucket\_internal) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a | | [s3bucket\_quarantine](#module\_s3bucket\_quarantine) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a | | [sqs\_letter\_render](#module\_sqs\_letter\_render) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | +| [sqs\_proof\_requests\_table\_events\_pipe\_dlq](#module\_sqs\_proof\_requests\_table\_events\_pipe\_dlq) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_routing\_config\_table\_events\_pipe\_dlq](#module\_sqs\_routing\_config\_table\_events\_pipe\_dlq) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_sftp\_upload](#module\_sqs\_sftp\_upload) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_template\_mgmt\_events](#module\_sqs\_template\_mgmt\_events) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | @@ -88,6 +89,7 @@ No requirements. | [download\_bucket\_name](#output\_download\_bucket\_name) | n/a | | [download\_bucket\_regional\_domain\_name](#output\_download\_bucket\_regional\_domain\_name) | n/a | | [internal\_bucket\_name](#output\_internal\_bucket\_name) | n/a | +| [proof\_requests\_table\_name](#output\_proof\_requests\_table\_name) | n/a | | [quarantine\_bucket\_name](#output\_quarantine\_bucket\_name) | n/a | | [request\_proof\_queue\_url](#output\_request\_proof\_queue\_url) | n/a | | [routing\_config\_table\_name](#output\_routing\_config\_table\_name) | n/a | diff --git a/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf b/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf new file mode 100644 index 000000000..e652d480c --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf @@ -0,0 +1,5 @@ +resource "aws_cloudwatch_log_group" "pipe_proof_requests_table_events" { + name = "/aws/vendedlogs/pipes/${local.csi}-proof-requests-table-events" + kms_key_id = var.kms_key_arn + retention_in_days = var.log_retention_in_days +} diff --git a/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf b/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf new file mode 100644 index 000000000..8183bbbed --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf @@ -0,0 +1,44 @@ +resource "aws_dynamodb_table" "proof_requests" { + name = "${local.csi}-proof-requests" + billing_mode = "PAY_PER_REQUEST" + + hash_key = "owner" + range_key = "id" + + attribute { + name = "owner" + type = "S" + } + + attribute { + name = "id" + type = "S" + } + + ttl { + attribute_name = "ttl" + enabled = true + } + + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = true + kms_key_arn = var.kms_key_arn + } + + tags = { + "NHSE-Enable-Dynamo-Backup" = var.enable_backup ? "True" : "False" + } + + lifecycle { + ignore_changes = [ + name, # To support backup and restore which will result in a new name otherwise + ] + } + + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" +} diff --git a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf index 8ab88e03b..dadd507a2 100644 --- a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf +++ b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf @@ -26,6 +26,7 @@ module "lambda_event_publisher" { lambda_env_vars = { EVENT_SOURCE = "//notify.nhs.uk/${var.component}/${var.group}/${var.environment}" + PROOF_REQUEST_TABLE_NAME = aws_dynamodb_table.proof_requests.name ROUTING_CONFIG_TABLE_NAME = aws_dynamodb_table.routing_configuration.name SNS_TOPIC_ARN = coalesce(var.sns_topic_arn, aws_sns_topic.main.arn) TEMPLATES_TABLE_NAME = aws_dynamodb_table.templates.name diff --git a/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf b/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf new file mode 100644 index 000000000..79856eff1 --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf @@ -0,0 +1,12 @@ +module "sqs_proof_requests_table_events_pipe_dlq" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip" + + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + name = "proof-requests-table-events-pipe-dead-letter" + sqs_kms_key_arn = var.kms_key_arn + message_retention_seconds = 1209600 +} diff --git a/infrastructure/terraform/modules/backend-api/outputs.tf b/infrastructure/terraform/modules/backend-api/outputs.tf index 0c43341b2..ed6030b55 100644 --- a/infrastructure/terraform/modules/backend-api/outputs.tf +++ b/infrastructure/terraform/modules/backend-api/outputs.tf @@ -45,3 +45,7 @@ output "quarantine_bucket_name" { output "routing_config_table_name" { value = aws_dynamodb_table.routing_configuration.name } + +output "proof_requests_table_name" { + value = aws_dynamodb_table.proof_requests.name +} diff --git a/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf b/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf new file mode 100644 index 000000000..16551e0d3 --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf @@ -0,0 +1,113 @@ +resource "aws_pipes_pipe" "proof_requests_table_events" { + depends_on = [module.sqs_proof_requests_table_events_pipe_dlq] + + name = "${local.csi}-proof-requests-table-events" + role_arn = aws_iam_role.pipe_proof_requests_table_events.arn + source = aws_dynamodb_table.proof_requests.stream_arn + target = module.sqs_template_mgmt_events.sqs_queue_arn + desired_state = "RUNNING" + kms_key_identifier = var.kms_key_arn + + source_parameters { + dynamodb_stream_parameters { + starting_position = "TRIM_HORIZON" + on_partial_batch_item_failure = "AUTOMATIC_BISECT" + batch_size = 10 + maximum_batching_window_in_seconds = 5 + maximum_retry_attempts = 5 + maximum_record_age_in_seconds = -1 + + dead_letter_config { + arn = module.sqs_proof_requests_table_events_pipe_dlq.sqs_queue_arn + } + } + } + + target_parameters { + input_template = <<-EOF + { + "dynamodb": <$.dynamodb>, + "eventID": <$.eventID>, + "eventName": <$.eventName>, + "eventSource": <$.eventSource>, + "tableName": "${aws_dynamodb_table.proof_requests.name}" + } + EOF + + sqs_queue_parameters { + message_group_id = "$.dynamodb.Keys.id.S" + message_deduplication_id = "$.eventID" + } + } + + log_configuration { + level = "ERROR" + include_execution_data = ["ALL"] + + cloudwatch_logs_log_destination { + log_group_arn = aws_cloudwatch_log_group.pipe_proof_requests_table_events.arn + } + } +} + +resource "aws_iam_role" "pipe_proof_requests_table_events" { + name = "${local.csi}-pipe-proof-requests-table-events" + description = "IAM Role for Pipe to forward proof requests table stream events to SQS" + assume_role_policy = data.aws_iam_policy_document.pipes_proof_requests_trust_policy.json +} + +data "aws_iam_policy_document" "pipes_proof_requests_trust_policy" { + statement { + sid = "PipesAssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["pipes.amazonaws.com"] + } + } +} + +resource "aws_iam_role_policy" "pipe_proof_requests_table_events" { + name = "${local.csi}-pipe-proof-requests-table-events" + role = aws_iam_role.pipe_proof_requests_table_events.id + policy = data.aws_iam_policy_document.pipe_proof_requests_table_events.json +} + +data "aws_iam_policy_document" "pipe_proof_requests_table_events" { + version = "2012-10-17" + + statement { + sid = "AllowDynamoStreamRead" + effect = "Allow" + actions = [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams", + ] + resources = [aws_dynamodb_table.proof_requests.stream_arn] + } + + statement { + sid = "AllowSqsSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [ + module.sqs_template_mgmt_events.sqs_queue_arn, + module.sqs_proof_requests_table_events_pipe_dlq.sqs_queue_arn, + ] + } + + statement { + sid = "AllowKmsUsage" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*" + ] + resources = [var.kms_key_arn] + } +} diff --git a/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts b/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts index ab5ba1615..b5da3f675 100644 --- a/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts +++ b/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts @@ -23,11 +23,13 @@ const { logger: mockLogger } = createMockLogger(); const tables = { templates: 'templates-table', routing: 'routing-config-table', + proofRequests: 'proof-request-table', }; const eventBuilder = new EventBuilder( tables.templates, tables.routing, + tables.proofRequests, 'event-source', mockLogger ); @@ -378,6 +380,67 @@ const expectedRoutingConfigEvent = ( }, }); +const publishableProofRequestEventRecord = (): PublishableEventRecord => ({ + dynamodb: { + SequenceNumber: '4', + NewImage: { + id: { + S: '92b676e9-470f-4d04-ab14-965ef145e15d', + }, + templateId: { + S: 'bed3398c-bbe3-435d-80c1-58154d4bf7dd', + }, + templateType: { + S: 'SMS', + }, + testPatientNhsNumber: { + S: '9000000009', + }, + contactDetails: { + M: { + sms: { + S: '07700900000', + }, + }, + }, + personalisation: { + M: { + firstName: { + S: 'Jane', + }, + }, + }, + }, + }, + eventID: '7f2ae4b0-82c2-4911-9b84-8997d7f3f40d', + tableName: tables.proofRequests, +}); + +const expectedProofRequestedEvent = () => ({ + id: '7f2ae4b0-82c2-4911-9b84-8997d7f3f40d', + datacontenttype: 'application/json', + time: '2022-01-01T09:00:00.000Z', + source: 'event-source', + type: 'uk.nhs.notify.template-management.ProofRequested.v1', + specversion: '1.0', + dataschema: 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json', + dataschemaversion: VERSION, + plane: 'data', + subject: '92b676e9-470f-4d04-ab14-965ef145e15d', + data: { + id: '92b676e9-470f-4d04-ab14-965ef145e15d', + templateId: 'bed3398c-bbe3-435d-80c1-58154d4bf7dd', + templateType: 'SMS', + testPatientNhsNumber: '9000000009', + contactDetails: { + sms: '07700900000', + }, + personalisation: { + firstName: 'Jane', + }, + }, +}); + test('errors on unrecognised event table source', () => { const invalidpublishableTemplateEventRecord = { ...publishableTemplateEventRecord('SUBMITTED'), @@ -634,3 +697,55 @@ describe('routing config events', () => { expect(event).toEqual(undefined); }); }); + +describe('proof request events', () => { + test('builds proof requested event', () => { + const event = eventBuilder.buildEvent(publishableProofRequestEventRecord()); + + expect(event).toEqual(expectedProofRequestedEvent()); + }); + + test('errors on output schema validation failure after input parsing', () => { + const valid = publishableProofRequestEventRecord(); + + const invalidDomainEventRecord: PublishableEventRecord = { + ...valid, + dynamodb: { + ...valid.dynamodb, + NewImage: { + ...valid.dynamodb.NewImage, + templateType: { S: 'LETTER' }, + }, + }, + }; + + expect(() => eventBuilder.buildEvent(invalidDomainEventRecord)).toThrow( + expect.objectContaining({ + name: 'ZodError', + issues: [ + expect.objectContaining({ + code: 'invalid_value', + values: ['NHS_APP', 'SMS', 'EMAIL'], + path: ['data', 'templateType'], + }), + ], + }) + ); + }); + + test('does not build proof request event on hard delete', () => { + const hardDeletePublishableProofRequestEventRecord = { + ...publishableProofRequestEventRecord(), + dynamodb: { + SequenceNumber: '4', + NewImage: undefined, + }, + }; + + const event = eventBuilder.buildEvent( + hardDeletePublishableProofRequestEventRecord + ); + + expect(event).toEqual(undefined); + }); +}); diff --git a/lambdas/event-publisher/src/config.ts b/lambdas/event-publisher/src/config.ts index 977ec2660..27060b776 100644 --- a/lambdas/event-publisher/src/config.ts +++ b/lambdas/event-publisher/src/config.ts @@ -5,6 +5,7 @@ const $Config = z.object({ ROUTING_CONFIG_TABLE_NAME: z.string(), SNS_TOPIC_ARN: z.string(), TEMPLATES_TABLE_NAME: z.string(), + PROOF_REQUEST_TABLE_NAME: z.string(), }); export const loadConfig = () => { diff --git a/lambdas/event-publisher/src/container.ts b/lambdas/event-publisher/src/container.ts index 3199d25cd..9c9c0da48 100644 --- a/lambdas/event-publisher/src/container.ts +++ b/lambdas/event-publisher/src/container.ts @@ -11,6 +11,7 @@ export const createContainer = () => { ROUTING_CONFIG_TABLE_NAME, SNS_TOPIC_ARN, TEMPLATES_TABLE_NAME, + PROOF_REQUEST_TABLE_NAME, } = loadConfig(); const snsClient = new SNSClient({ region: 'eu-west-2' }); @@ -20,6 +21,7 @@ export const createContainer = () => { const eventBuilder = new EventBuilder( TEMPLATES_TABLE_NAME, ROUTING_CONFIG_TABLE_NAME, + PROOF_REQUEST_TABLE_NAME, EVENT_SOURCE, logger ); diff --git a/lambdas/event-publisher/src/domain/event-builder.ts b/lambdas/event-publisher/src/domain/event-builder.ts index d6d401333..6576c0166 100644 --- a/lambdas/event-publisher/src/domain/event-builder.ts +++ b/lambdas/event-publisher/src/domain/event-builder.ts @@ -5,6 +5,7 @@ import { } from '@nhsdigital/nhs-notify-event-schemas-template-management'; import { Logger } from 'nhs-notify-web-template-management-utils/logger'; import { + $DynamoDBProofRequest, $DynamoDBRoutingConfig, $DynamoDBTemplate, PublishableEventRecord, @@ -16,6 +17,7 @@ export class EventBuilder { constructor( private readonly templatesTableName: string, private readonly routingConfigTableName: string, + private readonly proofRequestTableName: string, private readonly eventSource: string, private readonly logger: Logger ) {} @@ -52,14 +54,19 @@ export class EventBuilder { } } - private buildEventMetadata(id: string, type: string, subject: string) { + private buildEventMetadata( + id: string, + type: string, + subject: string, + plane: 'control' | 'data' = 'control' + ) { return { id, datacontenttype: 'application/json', time: new Date().toISOString(), source: this.eventSource, specversion: '1.0', - plane: 'control', + plane, subject, type: `uk.nhs.notify.template-management.${type}.v${MAJOR_VERSION}`, dataschema: `https://notify.nhs.uk/events/schemas/${type}/v${MAJOR_VERSION}.json`, @@ -165,6 +172,45 @@ export class EventBuilder { return event.data; } + private buildProofRequestedEvent( + publishableEventRecord: PublishableEventRecord + ): Event | undefined { + if (!publishableEventRecord.dynamodb.NewImage) { + // No need to publish an event if a proof request has been deleted. + this.logger.debug({ + description: 'No new image found', + publishableEventRecord, + }); + + return undefined; + } + + const dynamoRecord = unmarshall(publishableEventRecord.dynamodb.NewImage); + + const databaseProofRequest = $DynamoDBProofRequest.parse(dynamoRecord); + + try { + return $Event.parse({ + ...this.buildEventMetadata( + publishableEventRecord.eventID, + 'ProofRequested', + databaseProofRequest.id, + 'data' + ), + data: dynamoRecord, + }); + } catch (error) { + this.logger + .child({ + description: 'Failed to parse outgoing event for proof request', + publishableEventRecord, + }) + .error(error); + + throw error; + } + } + buildEvent( publishableEventRecord: PublishableEventRecord ): Event | undefined { @@ -175,6 +221,9 @@ export class EventBuilder { case this.routingConfigTableName: { return this.buildRoutingConfigDatabaseEvent(publishableEventRecord); } + case this.proofRequestTableName: { + return this.buildProofRequestedEvent(publishableEventRecord); + } default: { this.logger.error({ description: 'Unrecognised event type', diff --git a/lambdas/event-publisher/src/domain/input-schemas.ts b/lambdas/event-publisher/src/domain/input-schemas.ts index b6b4bed70..51870ad46 100644 --- a/lambdas/event-publisher/src/domain/input-schemas.ts +++ b/lambdas/event-publisher/src/domain/input-schemas.ts @@ -53,6 +53,9 @@ export const $DynamoDBRoutingConfig = schemaFor>()( ); export type DynamoDBRoutingConfig = z.infer; +export const $DynamoDBProofRequest = z.object({ id: z.string() }); +export type DynamoDBProofRequest = z.infer; + // the lambda doesn't necessarily have to only accept inputs from a dynamodb stream via an // eventbridge pipe, but that's all it is doing at the moment export const $PublishableEventRecord = $DynamoDBStreamRecord; diff --git a/lambdas/event-publisher/src/domain/output-schemas.ts b/lambdas/event-publisher/src/domain/output-schemas.ts index e43f8b267..17c257c09 100644 --- a/lambdas/event-publisher/src/domain/output-schemas.ts +++ b/lambdas/event-publisher/src/domain/output-schemas.ts @@ -1,4 +1,5 @@ import { + $ProofRequestedEventV1, $RoutingConfigCompletedEventV1, $RoutingConfigDeletedEventV1, $RoutingConfigDraftedEventV1, @@ -9,11 +10,12 @@ import { import { z } from 'zod'; export const $Event = z.discriminatedUnion('type', [ - $TemplateCompletedEventV1, - $TemplateDraftedEventV1, - $TemplateDeletedEventV1, + $ProofRequestedEventV1, $RoutingConfigCompletedEventV1, - $RoutingConfigDraftedEventV1, $RoutingConfigDeletedEventV1, + $RoutingConfigDraftedEventV1, + $TemplateCompletedEventV1, + $TemplateDeletedEventV1, + $TemplateDraftedEventV1, ]); export type Event = z.infer; diff --git a/package-lock.json b/package-lock.json index 41c037ffa..64b5dd942 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7719,13 +7719,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.18.tgz", - "integrity": "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==", + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.19.tgz", + "integrity": "sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", @@ -7826,9 +7826,9 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.8.tgz", - "integrity": "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==", + "version": "3.996.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.9.tgz", + "integrity": "sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -7842,7 +7842,7 @@ "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", + "@aws-sdk/util-user-agent-node": "^3.973.6", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", @@ -7932,15 +7932,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.5.tgz", - "integrity": "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.6.tgz", + "integrity": "sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -7970,9 +7971,9 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -27883,7 +27884,7 @@ }, "packages/event-schemas": { "name": "@nhsdigital/nhs-notify-event-schemas-template-management", - "version": "1.4.4", + "version": "1.4.5", "license": "MIT", "dependencies": { "zod": "^4.0.17" diff --git a/packages/event-schemas/README.md b/packages/event-schemas/README.md index 11e021206..b64b044f9 100644 --- a/packages/event-schemas/README.md +++ b/packages/event-schemas/README.md @@ -19,6 +19,10 @@ Then run `npm install @nhsdigital/nhs-notify-event-schemas-template-management` ## Events +- `ProofRequested` +- `RoutingConfigCompleted` +- `RoutingConfigDeleted` +- `RoutingConfigDrafted` - `TemplateCompleted` - `TemplateDeleted` - `TemplateDrafted` diff --git a/packages/event-schemas/__tests__/events/proof-requested.test.ts b/packages/event-schemas/__tests__/events/proof-requested.test.ts new file mode 100644 index 000000000..ef72fb968 --- /dev/null +++ b/packages/event-schemas/__tests__/events/proof-requested.test.ts @@ -0,0 +1,26 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { $ProofRequestedEventV1 } from '../../src/events/proof-requested'; + +const examplesDir = path.resolve(__dirname, '../../examples/ProofRequested/v1'); + +describe('ProofRequestedEventV1 Zod schema', () => { + it.each(fs.readdirSync(examplesDir))( + 'parses sample event %s without errors', + (filename) => { + const event = JSON.parse( + fs.readFileSync(path.join(examplesDir, filename), 'utf8') + ); + + const result = $ProofRequestedEventV1.safeParse(event); + + if (!result.success) { + console.log(result.error); + } + + expect(result.success).toBe(true); + } + ); +}); diff --git a/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts b/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts new file mode 100644 index 000000000..aac9ab0bb --- /dev/null +++ b/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts @@ -0,0 +1,33 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { Ajv2020 } from 'ajv/dist/2020'; +import addFormats from 'ajv-formats'; + +import ProofRequestedEventV1Schema from '../../schemas/ProofRequested/v1.json'; + +const examplesDir = path.resolve(__dirname, '../../examples/ProofRequested/v1'); + +describe('ProofRequestedEventV1 JSON schema', () => { + it.each(fs.readdirSync(examplesDir))( + 'parses sample event %s without errors', + (filename) => { + const event = JSON.parse( + fs.readFileSync(path.join(examplesDir, filename), 'utf8') + ); + + const ajv = new Ajv2020(); + addFormats(ajv); + const validate = ajv.compile(ProofRequestedEventV1Schema); + + const valid = validate(event); + + if (!valid) { + console.log(validate.errors); + } + + expect(valid).toBe(true); + } + ); +}); diff --git a/packages/event-schemas/examples/ProofRequested/v1/email.json b/packages/event-schemas/examples/ProofRequested/v1/email.json new file mode 100644 index 000000000..63fa0783b --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/email.json @@ -0,0 +1,25 @@ +{ + "data": { + "id": "c3d4e5f6-a7b8-4012-8def-123456789012", + "templateId": "8c7ae592-97cd-4900-897e-ef4794c8a745", + "templateType": "EMAIL", + "testPatientNhsNumber": "9000000009", + "contactDetails": { + "email": "test.patient@example.com" + }, + "personalisation": { + "firstName": "Jane", + "surgeryName": "Test Surgery" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "b3c4d5e6-f7a8-9012-bcde-f12345678902", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "c3d4e5f6-a7b8-4012-8def-123456789012", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json b/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json new file mode 100644 index 000000000..063600877 --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "templateId": "3a58a370-75ab-4788-a75e-cd2572a68523", + "templateType": "NHS_APP", + "testPatientNhsNumber": "9000000009", + "personalisation": { + "firstName": "Jane", + "appointmentTime": "10:00", + "appointmentDate": "2025-08-01", + "surgeryName": "Test Surgery" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "f1e2d3c4-b5a6-7890-fedc-ba0987654321", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/examples/ProofRequested/v1/sms.json b/packages/event-schemas/examples/ProofRequested/v1/sms.json new file mode 100644 index 000000000..a80d05afc --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/sms.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "templateId": "7b69c481-86bc-5899-b86f-de3683b79634", + "templateType": "SMS", + "testPatientNhsNumber": "9000000009", + "contactDetails": { + "sms": "07700900000" + }, + "personalisation": { + "firstName": "Jane" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "a2b3c4d5-e6f7-8901-abcd-ef1234567891", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/package.json b/packages/event-schemas/package.json index 32a6a1f3c..60080f05b 100644 --- a/packages/event-schemas/package.json +++ b/packages/event-schemas/package.json @@ -56,5 +56,5 @@ }, "type": "commonjs", "types": "./dist/index.d.ts", - "version": "1.4.4" + "version": "1.4.5" } diff --git a/packages/event-schemas/schemas/ProofRequested/v1.json b/packages/event-schemas/schemas/ProofRequested/v1.json new file mode 100644 index 000000000..2b34a0437 --- /dev/null +++ b/packages/event-schemas/schemas/ProofRequested/v1.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 1000, + "description": "Unique ID for this event" + }, + "time": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$", + "description": "Time the event was generated" + }, + "type": { + "type": "string", + "const": "uk.nhs.notify.template-management.ProofRequested.v1" + }, + "source": { + "type": "string", + "description": "Source of the event" + }, + "specversion": { + "type": "string", + "const": "1.0", + "description": "Version of the envelope event schema" + }, + "datacontenttype": { + "type": "string", + "const": "application/json", + "description": "Always application/json" + }, + "subject": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "description": "Unique identifier for the item in the event data" + }, + "dataschema": { + "type": "string", + "const": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json" + }, + "dataschemaversion": { + "type": "string", + "pattern": "^1\\..*" + }, + "plane": { + "type": "string", + "const": "data" + }, + "data": { + "$ref": "#/$defs/ProofRequestEventData" + } + }, + "required": [ + "id", + "time", + "type", + "source", + "specversion", + "datacontenttype", + "subject", + "dataschema", + "dataschemaversion", + "plane", + "data" + ], + "$defs": { + "ProofRequestEventData": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[\\dA-Fa-f]{8}(?:-[\\dA-Fa-f]{4}){3}-[\\dA-Fa-f]{12}$", + "description": "Unique identifier of the proof request" + }, + "templateId": { + "type": "string", + "pattern": "^[\\dA-Fa-f]{8}(?:-[\\dA-Fa-f]{4}){3}-[\\dA-Fa-f]{12}$", + "description": "Unique identifier for the template being proofed" + }, + "templateType": { + "type": "string", + "enum": [ + "NHS_APP", + "SMS", + "EMAIL" + ], + "description": "Template type for the template being proofed" + }, + "testPatientNhsNumber": { + "type": "string", + "description": "NHS number of test patient to use in the proofing request" + }, + "contactDetails": { + "description": "Contact details to send the proof to", + "type": "object", + "properties": { + "sms": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "personalisation": { + "description": "Personalisation fields to use in the proof", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "id", + "templateId", + "templateType", + "testPatientNhsNumber" + ] + } + }, + "$id": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json" +} diff --git a/packages/event-schemas/scripts/generate-json-schemas.ts b/packages/event-schemas/scripts/generate-json-schemas.ts index 72ef759b5..ef5f6eab3 100644 --- a/packages/event-schemas/scripts/generate-json-schemas.ts +++ b/packages/event-schemas/scripts/generate-json-schemas.ts @@ -3,6 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { + $ProofRequestedEventV1, $TemplateCompletedEventV1, $TemplateDeletedEventV1, $TemplateDraftedEventV1, @@ -62,6 +63,13 @@ function writeSchema( ); } +writeSchema( + 'ProofRequested', + $ProofRequestedEventV1, + '1', + 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json' +); + writeSchema( 'TemplateCompleted', $TemplateCompletedEventV1, diff --git a/packages/event-schemas/src/events/index.ts b/packages/event-schemas/src/events/index.ts index fe4096163..eb1cc3332 100644 --- a/packages/event-schemas/src/events/index.ts +++ b/packages/event-schemas/src/events/index.ts @@ -1,6 +1,7 @@ -export * from './template-completed'; -export * from './template-deleted'; -export * from './template-drafted'; +export * from './proof-requested'; export * from './routing-config-completed'; export * from './routing-config-deleted'; export * from './routing-config-drafted'; +export * from './template-completed'; +export * from './template-deleted'; +export * from './template-drafted'; diff --git a/packages/event-schemas/src/events/proof-requested.ts b/packages/event-schemas/src/events/proof-requested.ts new file mode 100644 index 000000000..a31fc0280 --- /dev/null +++ b/packages/event-schemas/src/events/proof-requested.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { $NHSNotifyEventEnvelope } from '../event-envelope'; +import { $ProofRequestEventData } from '../proof-request'; + +export const $ProofRequestedEventV1 = $NHSNotifyEventEnvelope.extend({ + type: z.literal('uk.nhs.notify.template-management.ProofRequested.v1'), + dataschema: z.literal( + 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json' + ), + dataschemaversion: z.string().startsWith('1.'), + plane: z.literal('data'), + data: $ProofRequestEventData, +}); + +export type ProofRequestedEventV1 = z.infer; diff --git a/packages/event-schemas/src/proof-request.ts b/packages/event-schemas/src/proof-request.ts new file mode 100644 index 000000000..6cfbf2168 --- /dev/null +++ b/packages/event-schemas/src/proof-request.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +//eslint-disable-next-line security/detect-unsafe-regex +const UUID_REGEX = /^[\dA-Fa-f]{8}(?:-[\dA-Fa-f]{4}){3}-[\dA-Fa-f]{12}$/; + +export const $ProofRequestEventData = z + .object({ + id: z.string().regex(UUID_REGEX).meta({ + description: 'Unique identifier of the proof request', + }), + templateId: z.string().regex(UUID_REGEX).meta({ + description: 'Unique identifier for the template being proofed', + }), + templateType: z.enum(['NHS_APP', 'SMS', 'EMAIL']).meta({ + description: 'Template type for the template being proofed', + }), + testPatientNhsNumber: z.string().meta({ + description: 'NHS number of test patient to use in the proofing request', + }), + contactDetails: z + .object({ + sms: z.string().optional(), + email: z.string().optional(), + }) + .optional() + .meta({ description: 'Contact details to send the proof to' }), + personalisation: z.record(z.string(), z.string()).optional().meta({ + description: 'Personalisation fields to use in the proof', + }), + }) + .meta({ + id: 'ProofRequestEventData', + }); diff --git a/scripts/config/vale/styles/config/vocabularies/words/accept.txt b/scripts/config/vale/styles/config/vocabularies/words/accept.txt index 8d5dc5918..4824dbba9 100644 --- a/scripts/config/vale/styles/config/vocabularies/words/accept.txt +++ b/scripts/config/vale/styles/config/vocabularies/words/accept.txt @@ -27,3 +27,4 @@ Terraform toolchain Trufflehog Zod +[Vv]alidators diff --git a/tests/test-team/global.d.ts b/tests/test-team/global.d.ts index 6654bfe88..c8a6e126a 100644 --- a/tests/test-team/global.d.ts +++ b/tests/test-team/global.d.ts @@ -9,6 +9,7 @@ declare global { ENVIRONMENT: string; EVENTS_SNS_TOPIC_ARN: string; PLAYWRIGHT_RUN_ID: string; + PROOF_REQUESTS_TABLE_NAME: string; REQUEST_PROOF_QUEUE_URL: string; ROUTING_CONFIG_TABLE_NAME: string; SFTP_ENVIRONMENT: string; diff --git a/tests/test-team/helpers/db/proof-requests-storage-helper.ts b/tests/test-team/helpers/db/proof-requests-storage-helper.ts new file mode 100644 index 000000000..7d70a7fbb --- /dev/null +++ b/tests/test-team/helpers/db/proof-requests-storage-helper.ts @@ -0,0 +1,106 @@ +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { + BatchWriteCommand, + DynamoDBDocumentClient, +} from '@aws-sdk/lib-dynamodb'; +import type { ProofRequest } from '../types'; + +type ProofRequestKey = { id: string; owner: string }; + +export class ProofRequestsStorageHelper { + private readonly dynamo: DynamoDBDocumentClient = DynamoDBDocumentClient.from( + new DynamoDBClient({ region: 'eu-west-2' }) + ); + + private seedData: ProofRequest[] = []; + + private adHocKeys: ProofRequestKey[] = []; + + /** + * Seed a load of proof requests into the database + */ + async seed(data: ProofRequest[]) { + this.seedData.push(...data); + + const chunks = ProofRequestsStorageHelper.chunk(data); + + await Promise.all( + chunks.map(async (chunk) => { + await this.dynamo.send( + new BatchWriteCommand({ + RequestItems: { + [process.env.PROOF_REQUESTS_TABLE_NAME]: chunk.map( + (proofRequest) => ({ + PutRequest: { + Item: proofRequest, + }, + }) + ), + }, + }) + ); + }) + ); + } + + /** + * Delete proof requests seeded by calls to seed + */ + public async deleteSeeded() { + await this.delete( + this.seedData.map(({ id, owner }) => ({ + id, + owner, + })) + ); + this.seedData = []; + } + + private async delete(keys: ProofRequestKey[]) { + const dbChunks = ProofRequestsStorageHelper.chunk(keys); + + await Promise.all( + dbChunks.map((chunk) => + this.dynamo.send( + new BatchWriteCommand({ + RequestItems: { + [process.env.PROOF_REQUESTS_TABLE_NAME]: chunk.map((key) => ({ + DeleteRequest: { + Key: key, + }, + })), + }, + }) + ) + ) + ); + } + + /** + * Stores references to proof requests created in tests (not via seeding) + */ + public addAdHocKey(key: ProofRequestKey) { + this.adHocKeys.push(key); + } + + /** + * Delete proof requests referenced by calls to addAdHocKey from database + */ + async deleteAdHoc() { + await this.delete(this.adHocKeys); + this.adHocKeys = []; + } + + /** + * Breaks a list into chunks of upto 25 items + */ + private static chunk(list: T[], size = 25): T[][] { + const chunks: T[][] = []; + + for (let i = 0; i < list.length; i += size) { + chunks.push(list.slice(i, i + size)); + } + + return chunks; + } +} diff --git a/tests/test-team/helpers/types.ts b/tests/test-team/helpers/types.ts index 2fdf5ebfd..e2844598b 100644 --- a/tests/test-team/helpers/types.ts +++ b/tests/test-team/helpers/types.ts @@ -4,6 +4,7 @@ import type { RoutingConfig, Language, LetterType, + TemplateType, } from 'nhs-notify-web-template-management-types'; export const templateTypeDisplayMappings: Record = { @@ -133,3 +134,19 @@ export type FactoryRoutingConfigWithModifiers = FactoryRoutingConfig & { templateId?: string ) => FactoryRoutingConfigWithModifiers; }; + +type DigitalTemplateType = Extract; + +export type ProofRequest = { + id: string; + owner: string; + createdAt: string; + personalisation: Record; + contactDetails?: { + sms?: string; + email?: string; + }; + templateId: string; + templateType: DigitalTemplateType; + testPatientNhsNumber: string; +}; diff --git a/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts b/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts new file mode 100644 index 000000000..45a229aab --- /dev/null +++ b/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts @@ -0,0 +1,81 @@ +import { randomUUID } from 'node:crypto'; +import { + templateManagementEventSubscriber as test, + expect, +} from '../fixtures/template-management-event-subscriber'; +import { + createAuthHelper, + type TestUser, + testUsers, +} from '../helpers/auth/cognito-auth-helper'; +import { eventWithId } from '../helpers/events/matchers'; +import { ProofRequestsStorageHelper } from 'helpers/db/proof-requests-storage-helper'; + +test.describe('ProofRequestedEvent', () => { + const authHelper = createAuthHelper(); + const proofRequestsStorageHelper = new ProofRequestsStorageHelper(); + + let user: TestUser; + + test.beforeAll(async () => { + user = await authHelper.getTestUser(testUsers.User1.userId); + }); + + test.afterAll(async () => { + await proofRequestsStorageHelper.deleteSeeded(); + }); + + test('Expect a ProofRequestedEventv1 to be published when a proof request is created', async ({ + eventSubscriber, + }) => { + const start = new Date(); + + const proofRequestId = randomUUID(); + + // TODO: CCM-7941 - use API rather than directly into DB. + await proofRequestsStorageHelper.seed([ + { + id: proofRequestId, + owner: `CLIENT#${user.clientId}`, + createdAt: new Date().toISOString(), + personalisation: { + gpSurgery: 'Test GP Surgery', + }, + contactDetails: { + sms: '07999999999', + }, + templateId: randomUUID(), + templateType: 'SMS', + testPatientNhsNumber: '9999999999', + }, + ]); + + await expect(async () => { + const events = await eventSubscriber.receive({ + since: start, + match: eventWithId(proofRequestId), + }); + + expect(events).toHaveLength(1); + + expect(events).toContainEqual( + expect.objectContaining({ + record: expect.objectContaining({ + type: 'uk.nhs.notify.template-management.ProofRequested.v1', + data: expect.objectContaining({ + id: proofRequestId, + testPatientNhsNumber: '9999999999', + templateType: 'SMS', + personalisation: { + gpSurgery: 'Test GP Surgery', + }, + contactDetails: { + sms: '07999999999', + }, + }), + }), + }) + ); + }).toPass({ timeout: 60_000, intervals: [1000, 3000, 5000] }); + }); +}); diff --git a/utils/backend-config/src/backend-config.ts b/utils/backend-config/src/backend-config.ts index 6dace6a49..edbd1cab1 100644 --- a/utils/backend-config/src/backend-config.ts +++ b/utils/backend-config/src/backend-config.ts @@ -21,6 +21,7 @@ export type BackendConfig = { testEmailBucketPrefix: string; userPoolId: string; userPoolClientId: string; + proofRequestsTableName: string; }; export const BackendConfigHelper = { @@ -31,6 +32,7 @@ export const BackendConfigHelper = { clientSsmPathPrefix: process.env.CLIENT_SSM_PATH_PREFIX ?? '', environment: process.env.ENVIRONMENT ?? '', eventsSnsTopicArn: process.env.EVENTS_SNS_TOPIC_ARN ?? '', + proofRequestsTableName: process.env.PROOF_REQUESTS_TABLE_NAME ?? '', requestProofQueueUrl: process.env.REQUEST_PROOF_QUEUE_URL ?? '', routingConfigTableName: process.env.ROUTING_CONFIG_TABLE_NAME ?? '', sftpEnvironment: process.env.SFTP_ENVIRONMENT ?? '', @@ -72,6 +74,7 @@ export const BackendConfigHelper = { process.env.SFTP_POLL_LAMBDA_NAME = config.sftpPollLambdaName; process.env.TEST_EMAIL_BUCKET_NAME = config.testEmailBucketName; process.env.TEST_EMAIL_BUCKET_PREFIX = config.testEmailBucketPrefix; + process.env.PROOF_REQUESTS_TABLE_NAME = config.proofRequestsTableName; }, fromTerraformOutputsFile(filepath: string): BackendConfig { @@ -85,6 +88,8 @@ export const BackendConfigHelper = { outputsFileContent.client_ssm_path_prefix?.value ?? '', environment: deployment.environment ?? '', eventsSnsTopicArn: outputsFileContent.events_sns_topic_arn?.value ?? '', + proofRequestsTableName: + outputsFileContent.proof_requests_table_name?.value ?? '', requestProofQueueUrl: outputsFileContent.request_proof_queue_url?.value ?? '', routingConfigTableName: