Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions infrastructure/terraform/components/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ No requirements.
| Name | Source | Version |
|------|--------|---------|
| <a name="module_authorizer_lambda"></a> [authorizer\_lambda](#module\_authorizer\_lambda) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | v2.0.4 |
| <a name="module_get_letters"></a> [get\_letters](#module\_get\_letters) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | v2.0.10 |
| <a name="module_hello_world"></a> [hello\_world](#module\_hello\_world) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | v2.0.10 |
| <a name="module_kms"></a> [kms](#module\_kms) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/kms | v2.0.10 |
## Outputs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ data "aws_iam_policy_document" "api_gateway_execution_policy" {
resources = [
module.authorizer_lambda.function_arn,
module.hello_world.function_arn,
module.get_letters.function_arn,
]
}
}
1 change: 1 addition & 0 deletions infrastructure/terraform/components/api/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ locals {
AWS_REGION = var.region
AUTHORIZER_LAMBDA_ARN = module.authorizer_lambda.function_arn
HELLO_WORLD_LAMBDA_ARN = module.hello_world.function_arn
GET_LETTERS_LAMBDA_ARN = module.get_letters.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,56 @@
module "get_letters" {
source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda?ref=v2.0.10"

function_name = "get_letters"
description = "Get paginated letter ids"

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.get_letters_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 = "api-handler/dist"
function_include_common = true
handler_function_name = "getLetters"
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 = {
}
}

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

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

resources = [
module.kms.key_arn, ## Requires shared kms module
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module "hello_world" {
function_code_base_path = local.aws_lambda_functions_dir_path
function_code_dir = "api-handler/dist"
function_include_common = true
handler_function_name = "handler"
handler_function_name = "helloWorld"
runtime = "nodejs22.x"
memory = 128
timeout = 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"components": {
"securitySchemes": {
"LambdaAuthorizer": {
"type": "apiKey",
"name": "Authorization",
"in": "header",
"x-amazon-apigateway-authtype": "custom",
"name": "Authorization",
"type": "apiKey",
"x-amazon-apigateway-authorizer": {
"type": "request",
"authorizerUri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${AUTHORIZER_LAMBDA_ARN}/invocations",
"authorizerCredentials": "${APIG_EXECUTION_ROLE_ARN}",
"authorizerResultTtlInSeconds": 0,
"authorizerUri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${AUTHORIZER_LAMBDA_ARN}/invocations",
"identitySource": "method.request.header.Authorization",
"authorizerResultTtlInSeconds": 0
}
"type": "request"
},
"x-amazon-apigateway-authtype": "custom"
}
}
},
Expand All @@ -25,13 +25,18 @@
"paths": {
"/": {
"get": {
"summary": "Health check",
"description": "Returns 200 OK if the API is up.",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"LambdaAuthorizer": []
}
],
"summary": "Health check",
"x-amazon-apigateway-integration": {
"contentHandling": "CONVERT_TO_TEXT",
"credentials": "${APIG_EXECUTION_ROLE_ARN}",
Expand All @@ -45,12 +50,37 @@
"timeoutInMillis": 29000,
"type": "AWS_PROXY",
"uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${HELLO_WORLD_LAMBDA_ARN}/invocations"
}
}
},
"/letters": {
"get": {
"description": "Returns 200 OK with paginated letter ids.",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"LambdaAuthorizer": []
}
]
],
"summary": "Get letters",
"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/${GET_LETTERS_LAMBDA_ARN}/invocations"
}
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions lambdas/api-handler/src/handlers/__tests__/get-letters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getLetters } from '../../index';
import type { Context } from 'aws-lambda';
import { mockDeep } from 'jest-mock-extended';

describe('API Lambda handler', () => {
it('returns 200 OK with basic paginated resources', async () => {
const event = { path: '/letters' };
const context = mockDeep<Context>();
const callback = jest.fn();
const result = await getLetters(event, context, callback);

const expected = {
"links": {
"self": "/letters?page=1",
"first": "/letters?page=1",
"last": "/letters?page=1",
"next": "/letters?page=1",
"prev": "/letters?page=1"
},
"data": [
{ "type": "letter", "id": "l1" },
{ "type": "letter", "id": "l2" },
{ "type": "letter", "id": "l3" }
]
}

expect(result).toEqual({
statusCode: 200,
body: JSON.stringify(expected, null, 2),
});
});

it('returns 404 Not Found for an unknown path', async () => {
const event = { path: '/unknown' };
const context = mockDeep<Context>();
const callback = jest.fn();
const result = await getLetters(event, context, callback);

expect(result).toEqual({
statusCode: 404,
body: 'Not Found',
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { handler } from '../index';
import { helloWorld } from '../../index';
import type { Context } from 'aws-lambda';
import { mockDeep } from 'jest-mock-extended';

Expand All @@ -7,7 +7,7 @@ describe('API Lambda handler', () => {
const event = { path: '/' };
const context = mockDeep<Context>();
const callback = jest.fn();
const result = await handler(event, context, callback);
const result = await helloWorld(event, context, callback);

expect(result).toEqual({
statusCode: 200,
Expand All @@ -19,7 +19,7 @@ describe('API Lambda handler', () => {
const event = {}; // No path provided
const context = mockDeep<Context>();
const callback = jest.fn();
const result = await handler(event as any, context, callback);
const result = await helloWorld(event as any, context, callback);

expect(result).toEqual({
statusCode: 200,
Expand All @@ -31,7 +31,7 @@ describe('API Lambda handler', () => {
const event = { path: '/unknown' };
const context = mockDeep<Context>();
const callback = jest.fn();
const result = await handler(event, context, callback);
const result = await helloWorld(event, context, callback);

expect(result).toEqual({
statusCode: 404,
Expand Down
58 changes: 58 additions & 0 deletions lambdas/api-handler/src/handlers/get-letters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Handler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const storedLetters: string[] = ["l1", "l2", "l3"];

export const getLetters: Handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {

if (event.path === '/letters') {

const response = createGetLettersResponse(event.path, storedLetters);

return {
statusCode: 200,
body: JSON.stringify(response, null, 2)
};
}

return {
statusCode: 404,
body: 'Not Found',
};
};

interface GetLettersLinks {
self: string;
first: string;
last: string;
next?: string;
prev?: string;
}

interface Resource {
type: string;
id: string;
}

interface GetLettersResponse {
links: GetLettersLinks;
data: Resource[];
}

function createGetLettersResponse(
baseUrl: string,
letters: string[]
): GetLettersResponse {
return {
links: {
self: `${baseUrl}?page=1`,
first: `${baseUrl}?page=1`,
last: `${baseUrl}?page=1`,
next: `${baseUrl}?page=1`,
prev: `${baseUrl}?page=1`
},
data: letters.map((letterId) => ({
type: "letter",
id: letterId,
})),
};
}
18 changes: 18 additions & 0 deletions lambdas/api-handler/src/handlers/hello-world.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Replace me with the actual code for your Lambda function
import { Handler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const helloWorld: Handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const path = event.path || '/';

if (path === '/') {
return {
statusCode: 200,
body: 'Hello World',
};
}

return {
statusCode: 404,
body: 'Not Found',
};
};
21 changes: 3 additions & 18 deletions lambdas/api-handler/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
// Replace me with the actual code for your Lambda function
import { Handler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler: Handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const path = event.path || '/';

if (path === '/') {
return {
statusCode: 200,
body: 'Hello World',
};
}

return {
statusCode: 404,
body: 'Not Found',
};
};
// Export all handlers for ease of access
export { getLetters } from './handlers/get-letters';
export { helloWorld } from './handlers/hello-world';
1 change: 0 additions & 1 deletion lambdas/example-lambda/.eslintignore

This file was deleted.

4 changes: 0 additions & 4 deletions lambdas/example-lambda/.gitignore

This file was deleted.

Loading
Loading