Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
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
3 changes: 3 additions & 0 deletions infrastructure/terraform/components/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ No requirements.
| <a name="module_get_letter_data"></a> [get\_letter\_data](#module\_get\_letter\_data) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
| <a name="module_get_letters"></a> [get\_letters](#module\_get\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/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_logging_bucket"></a> [logging\_bucket](#module\_logging\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/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.24/terraform-lambda.zip | n/a |
| <a name="module_post_letters"></a> [post\_letters](#module\_post\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
| <a name="module_post_mi"></a> [post\_mi](#module\_post\_mi) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
| <a name="module_s3bucket_test_letters"></a> [s3bucket\_test\_letters](#module\_s3bucket\_test\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-s3bucket.zip | n/a |
| <a name="module_supplier_ssl"></a> [supplier\_ssl](#module\_supplier\_ssl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-ssl.zip | n/a |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "aws_lambda_event_source_mapping" "status_updates_sqs_to_status_update_handler" {
event_source_arn = module.letter_status_updates_queue.sqs_queue_arn
function_name = module.letter_status_update.function_arn
batch_size = 10
maximum_batching_window_in_seconds = 1
scaling_config { maximum_concurrency = 10 }

Comment thread
masl2 marked this conversation as resolved.
depends_on = [
module.letter_status_updates_queue, # ensures queue exists
module.letter_status_update # ensures update handler exists
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ data "aws_iam_policy_document" "api_gateway_execution_policy" {
module.get_letter_data.function_arn,
module.get_letters.function_arn,
module.patch_letter.function_arn,
module.post_letters.function_arn,
module.post_mi.function_arn
]
}
Expand Down
17 changes: 9 additions & 8 deletions infrastructure/terraform/components/api/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ locals {
root_domain_nameservers = local.acct.route53_zone_nameservers["supplier-api"]

openapi_spec = templatefile("${path.module}/resources/spec.tmpl.json", {
APIG_EXECUTION_ROLE_ARN = aws_iam_role.api_gateway_execution_role.arn
AWS_REGION = var.region
AUTHORIZER_LAMBDA_ARN = module.authorizer_lambda.function_arn
GET_LETTER_LAMBDA_ARN = module.get_letter.function_arn
GET_LETTERS_LAMBDA_ARN = module.get_letters.function_arn
GET_LETTER_DATA_LAMBDA_ARN = module.get_letter_data.function_arn
PATCH_LETTER_LAMBDA_ARN = module.patch_letter.function_arn
POST_MI_LAMBDA_ARN = module.post_mi.function_arn
APIG_EXECUTION_ROLE_ARN = aws_iam_role.api_gateway_execution_role.arn
AWS_REGION = var.region
AUTHORIZER_LAMBDA_ARN = module.authorizer_lambda.function_arn
GET_LETTER_LAMBDA_ARN = module.get_letter.function_arn
GET_LETTERS_LAMBDA_ARN = module.get_letters.function_arn
GET_LETTER_DATA_LAMBDA_ARN = module.get_letter_data.function_arn
PATCH_LETTER_LAMBDA_ARN = module.patch_letter.function_arn
POST_LETTERS_LAMBDA_ARN = module.post_letters.function_arn
POST_MI_LAMBDA_ARN = module.post_mi.function_arn
})

destination_arn = "arn:aws:logs:${var.region}:${var.shared_infra_account_id}:destination:nhs-main-obs-firehose-logs"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module "letter_status_update" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip"

function_name = "letter_status_update"
description = "Processes 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_status_update.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 = "api-handler/dist"
function_include_common = true
handler_function_name = "letterStatusUpdate"
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, {})
}

data "aws_iam_policy_document" "letter_status_update" {
statement {
sid = "KMSPermissions"
effect = "Allow"

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

resources = [
module.kms.key_arn, ## Requires shared kms module
]
}

statement {
sid = "AllowDynamoDBAccess"
effect = "Allow"

actions = [
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:UpdateItem",
]

resources = [
aws_dynamodb_table.letters.arn,
]
}

statement {
sid = "AllowQueueAccess"
effect = "Allow"

actions = [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
"sqs:ChangeMessageVisibility"
]

resources = [
module.letter_status_updates_queue.sqs_queue_arn
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module "post_letters" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip"

function_name = "post_letters"
description = "Receives and accepts collection of letters to update"

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.post_letters.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 = "api-handler/dist"
function_include_common = true
handler_function_name = "postLetters"
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, {
QUEUE_URL = module.letter_status_updates_queue.sqs_queue_url,
MAX_LIMIT = var.max_get_limit
})
}

data "aws_iam_policy_document" "post_letters" {
statement {
sid = "KMSPermissions"
effect = "Allow"

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

resources = [
module.kms.key_arn, ## Requires shared kms module
]
}

statement {
sid = "AllowQueueAccess"
effect = "Allow"

actions = [
"sqs:SendMessage",
"sqs:GetQueueAttributes",
]

resources = [
module.letter_status_updates_queue.sqs_queue_arn
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Queue to transport update letter status messages
module "letter_status_updates_queue" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip"

name = "letter_status_updates_queue"

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

sqs_kms_key_arn = module.kms.key_arn

create_dlq = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,43 @@
"type": "AWS_PROXY",
"uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${GET_LETTERS_LAMBDA_ARN}/invocations"
}
},
"post": {
"description": "Update the status of a collection of letters.",
"operationId": "postLetters",
"requestBody": {
"required": true
},
"responses": {
"202": {
"description": "Acknowledges letters will be updated"
},
"400": {
"description": "Bad request, invalid input data"
},
"500": {
"description": "Server error"
}
},
"security": [
{
"LambdaAuthorizer": []
}
],
"x-amazon-apigateway-integration": {
"contentHandling": "CONVERT_TO_TEXT",
"credentials": "${APIG_EXECUTION_ROLE_ARN}",
"httpMethod": "POST",
"passthroughBehavior": "WHEN_NO_TEMPLATES",
"responses": {
".*": {
"statusCode": "200"
}
},
"timeoutInMillis": 29000,
"type": "AWS_PROXY",
"uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${POST_LETTERS_LAMBDA_ARN}/invocations"
}
}
},
"/letters/{id}": {
Expand Down
1 change: 1 addition & 0 deletions lambdas/api-handler/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"dependencies": {
"@aws-sdk/client-sqs": "^3.925.0",
"@internal/datastore": "*",
"@internal/helpers": "*",
"esbuild": "^0.25.11",
Expand Down
8 changes: 8 additions & 0 deletions lambdas/api-handler/src/config/__tests__/deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ describe('createDependenciesContainer', () => {
S3Client: jest.fn(),
}));

jest.mock('@aws-sdk/client-sqs', () => ({
SQSClient: jest.fn(),
}));

// Repo client
jest.mock('@internal/datastore', () => ({
LetterRepository: jest.fn(),
Expand All @@ -45,6 +49,7 @@ describe('createDependenciesContainer', () => {
test('constructs deps and wires repository config correctly', async () => {
// get current mock instances
const { S3Client } = jest.requireMock('@aws-sdk/client-s3') as { S3Client: jest.Mock };
const { SQSClient } = jest.requireMock('@aws-sdk/client-sqs') as { SQSClient: jest.Mock };
const pinoMock = jest.requireMock('pino') as { default: jest.Mock };
const { LetterRepository, MIRepository } = jest.requireMock('@internal/datastore') as {
LetterRepository: jest.Mock,
Expand All @@ -55,6 +60,9 @@ describe('createDependenciesContainer', () => {
const deps: Deps = createDependenciesContainer();

expect(S3Client).toHaveBeenCalledTimes(1);

expect(SQSClient).toHaveBeenCalledTimes(1);

expect(pinoMock.default).toHaveBeenCalledTimes(1);

expect(LetterRepository).toHaveBeenCalledTimes(1);
Expand Down
2 changes: 2 additions & 0 deletions lambdas/api-handler/src/config/__tests__/env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('lambdaEnv', () => {
process.env.MI_TTL_HOURS = '2160';
process.env.DOWNLOAD_URL_TTL_SECONDS = '60';
process.env.MAX_LIMIT = '2500';
process.env.QUEUE_URL = 'url';

const { envVars } = require('../env');

Expand All @@ -33,6 +34,7 @@ describe('lambdaEnv', () => {
MI_TTL_HOURS: 2160,
DOWNLOAD_URL_TTL_SECONDS: 60,
MAX_LIMIT: 2500,
QUEUE_URL: 'url'
});
});

Expand Down
22 changes: 11 additions & 11 deletions lambdas/api-handler/src/config/deps.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { S3Client } from "@aws-sdk/client-s3";
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { SQSClient } from "@aws-sdk/client-sqs";
import pino from 'pino';
import { LetterRepository, MIRepository } from '../../../../internal/datastore';
import { envVars, EnvVars } from "../config/env";

export type Deps = {
s3Client: S3Client;
sqsClient: SQSClient;
letterRepo: LetterRepository;
miRepo: MIRepository;
logger: pino.Logger;
Expand All @@ -18,37 +20,35 @@ function createDocumentClient(): DynamoDBDocumentClient {
return DynamoDBDocumentClient.from(ddbClient);
}

function createLetterRepository(documentClient: DynamoDBDocumentClient, log: pino.Logger, envVars: EnvVars): LetterRepository {
const ddbClient = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(ddbClient);
function createLetterRepository(log: pino.Logger, envVars: EnvVars): LetterRepository {

const config = {
lettersTableName: envVars.LETTERS_TABLE_NAME,
lettersTtlHours: envVars.LETTER_TTL_HOURS
};

return new LetterRepository(docClient, log, config);
return new LetterRepository(createDocumentClient(), log, config);
}

function createMIRepository(documentClient: DynamoDBDocumentClient, log: pino.Logger, envVars: EnvVars): MIRepository {
const ddbClient = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(ddbClient);
function createMIRepository(log: pino.Logger, envVars: EnvVars): MIRepository {

const config = {
miTableName: envVars.MI_TABLE_NAME,
miTtlHours: envVars.MI_TTL_HOURS
};

return new MIRepository(docClient, log, config);
return new MIRepository(createDocumentClient(), log, config);
}

export function createDependenciesContainer(): Deps {

const log = pino();
const documentClient = createDocumentClient();

return {
s3Client: new S3Client(),
letterRepo: createLetterRepository(documentClient, log, envVars),
miRepo: createMIRepository(documentClient, log, envVars),
sqsClient: new SQSClient(),
letterRepo: createLetterRepository(log, envVars),
miRepo: createMIRepository(log, envVars),
logger: log,
env: envVars
};
Expand Down
Loading
Loading