Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e7ea643
Change env var name
francisco-videira-nhs Sep 26, 2025
6f006a7
fix env var name
francisco-videira-nhs Sep 29, 2025
6e5bf07
Merge remote-tracking branch 'origin/main' into feature/CCM-11188_2
francisco-videira-nhs Sep 29, 2025
c94892f
Refactor contracts and mappers
francisco-videira-nhs Oct 2, 2025
094289f
Merge remote-tracking branch 'origin/main' into feature/CCM-11188_2
francisco-videira-nhs Oct 2, 2025
989792f
Fix invalid error message
francisco-videira-nhs Oct 2, 2025
681ee2b
Clean up updateLetterStatus
francisco-videira-nhs Oct 2, 2025
8a8bcc4
Merge remote-tracking branch 'origin/main' into feature/CCM-11188_2
francisco-videira-nhs Oct 3, 2025
3a5da13
patchLetters to patchLetter
francisco-videira-nhs Oct 3, 2025
e2ae5fb
Fix sonar issues
francisco-videira-nhs Oct 3, 2025
520be45
Read headers in lower case
francisco-videira-nhs Oct 6, 2025
813c409
wip
francisco-videira-nhs Oct 6, 2025
0d5b6d6
wip
francisco-videira-nhs Oct 8, 2025
ee5c3ea
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 8, 2025
933524f
Add tf and tests
francisco-videira-nhs Oct 8, 2025
a39a042
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 8, 2025
40f7580
Fix tf
francisco-videira-nhs Oct 8, 2025
a38c8f8
Fix spec and return code
francisco-videira-nhs Oct 9, 2025
d670dbb
Fix spec
francisco-videira-nhs Oct 9, 2025
b879ed4
Fix spec
francisco-videira-nhs Oct 9, 2025
f4ed8d2
Fix tf
francisco-videira-nhs Oct 9, 2025
f2cf557
Fix export
francisco-videira-nhs Oct 9, 2025
95bc137
Fix location header
francisco-videira-nhs Oct 9, 2025
d1e5da3
Fix tests and tf
francisco-videira-nhs Oct 9, 2025
5bfa8dd
Add aws cli in devcontainer
francisco-videira-nhs Oct 9, 2025
0c80eed
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 9, 2025
d93c073
Improve dependency injection
francisco-videira-nhs Oct 13, 2025
957c732
Fix sonar attempt
francisco-videira-nhs Oct 13, 2025
aa36690
Add tests for sonar coverage
francisco-videira-nhs Oct 14, 2025
8fcb28f
Add url ttl as config
francisco-videira-nhs Oct 15, 2025
b7e6fc6
Add peer review suggestions
francisco-videira-nhs Oct 16, 2025
f8881cb
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 16, 2025
bfdc5fd
Remove non null assertion from env
francisco-videira-nhs Oct 16, 2025
fbb28c0
Fix lambdas ddb permissions
francisco-videira-nhs Oct 16, 2025
a4c7f26
Add dependency container
francisco-videira-nhs Oct 19, 2025
d37e735
Bump shared lambda module
francisco-videira-nhs Oct 19, 2025
7d9a3f2
Refactor header validation
francisco-videira-nhs Oct 19, 2025
1d0a6a5
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 21, 2025
b91e208
Last peer review actions
francisco-videira-nhs Oct 22, 2025
f2928fd
Merge remote-tracking branch 'origin/main' into feature/CCM-11603
francisco-videira-nhs Oct 22, 2025
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
18 changes: 8 additions & 10 deletions lambdas/api-handler/src/handlers/get-letter-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIGatewayProxyHandler } from "aws-lambda";
import { assertNotEmpty, lowerCaseKeys } from "../utils/validation";
import { assertNotEmpty, validateCommonHeaders } from "../utils/validation";
import { ApiErrorDetail } from '../contracts/errors';
import { mapErrorToResponse } from "../mappers/error-mapper";
import { ValidationError } from "../errors";
Expand All @@ -11,28 +11,26 @@ export function createGetLetterDataHandler(deps: Deps): APIGatewayProxyHandler {

return async (event) => {

let correlationId: string | undefined;
const commonHeadersResult = validateCommonHeaders(event.headers, deps);

if (!commonHeadersResult.ok) {
return mapErrorToResponse(commonHeadersResult.error, commonHeadersResult.correlationId, deps.logger);
}

try {
assertNotEmpty(event.headers, new Error("The request headers are empty"));
const lowerCasedHeaders = lowerCaseKeys(event.headers);
correlationId = assertNotEmpty(lowerCasedHeaders[deps.env.APIM_CORRELATION_HEADER],
new Error("The request headers don't contain the APIM correlation id"));
const supplierId = assertNotEmpty(lowerCasedHeaders[deps.env.SUPPLIER_ID_HEADER],
new ValidationError(ApiErrorDetail.InvalidRequestMissingSupplierId));
const letterId = assertNotEmpty( event.pathParameters?.id,
new ValidationError(ApiErrorDetail.InvalidRequestMissingLetterIdPathParameter));

return {
statusCode: 303,
headers: {
'Location': await getLetterDataUrl(supplierId, letterId, deps)
'Location': await getLetterDataUrl(commonHeadersResult.value.supplierId, letterId, deps)
},
body: ''
};
}
catch (error) {
return mapErrorToResponse(error, correlationId, deps.logger);
return mapErrorToResponse(error, commonHeadersResult.value.correlationId, deps.logger);
}
}
};
22 changes: 10 additions & 12 deletions lambdas/api-handler/src/handlers/get-letters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { APIGatewayProxyEventQueryStringParameters, APIGatewayProxyHandler } from "aws-lambda";
import { getLettersForSupplier } from "../services/letter-operations";
import { assertNotEmpty, lowerCaseKeys } from "../utils/validation";
import { validateCommonHeaders } from "../utils/validation";
import { ApiErrorDetail } from '../contracts/errors';
import { mapErrorToResponse } from "../mappers/error-mapper";
import { ValidationError } from "../errors";
Expand All @@ -19,21 +19,19 @@ export function createGetLettersHandler(deps: Deps): APIGatewayProxyHandler {

return async (event) => {

const { maxLimit } = getMaxLimit();
const commonHeadersResult = validateCommonHeaders(event.headers, deps);

if (!commonHeadersResult.ok) {
return mapErrorToResponse(commonHeadersResult.error, commonHeadersResult.correlationId, deps.logger);
}

let correlationId: string | undefined;
const { maxLimit } = getMaxLimit();

try {
assertNotEmpty(event.headers, new Error("The request headers are empty"));
const lowerCasedHeaders = lowerCaseKeys(event.headers);
correlationId = assertNotEmpty(lowerCasedHeaders[deps.env.APIM_CORRELATION_HEADER],
new Error("The request headers don't contain the APIM correlation id"));
const supplierId = assertNotEmpty(lowerCasedHeaders[deps.env.SUPPLIER_ID_HEADER],
new ValidationError(ApiErrorDetail.InvalidRequestMissingSupplierId));
const limitNumber = getLimitOrDefault(event.queryStringParameters, maxLimit, deps.logger);

const letters = await getLettersForSupplier(
supplierId,
commonHeadersResult.value.supplierId,
status,
limitNumber,
deps.letterRepo,
Expand All @@ -43,7 +41,7 @@ export function createGetLettersHandler(deps: Deps): APIGatewayProxyHandler {

deps.logger.info({
description: 'Pending letters successfully fetched',
supplierId,
supplierId: commonHeadersResult.value.supplierId,
limitNumber,
status,
lettersCount: letters.length
Expand All @@ -55,7 +53,7 @@ export function createGetLettersHandler(deps: Deps): APIGatewayProxyHandler {
};
}
catch (error) {
return mapErrorToResponse(error, correlationId, deps.logger);
return mapErrorToResponse(error, commonHeadersResult.value.correlationId, deps.logger);
}
}
};
Expand Down
20 changes: 9 additions & 11 deletions lambdas/api-handler/src/handlers/patch-letter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PatchLetterRequest, PatchLetterRequestSchema } from '../contracts/lette
import { ApiErrorDetail } from '../contracts/errors';
import { ValidationError } from '../errors';
import { mapErrorToResponse } from '../mappers/error-mapper';
import { assertNotEmpty, lowerCaseKeys } from '../utils/validation';
import { assertNotEmpty, validateCommonHeaders } from '../utils/validation';
import { mapToLetterDto } from '../mappers/letter-mapper';
import type { Deps } from "../config/deps";

Expand All @@ -13,15 +13,13 @@ export function createPatchLetterHandler(deps: Deps): APIGatewayProxyHandler {

return async (event) => {

let correlationId: string | undefined;
const commonHeadersResult = validateCommonHeaders(event.headers, deps);

if (!commonHeadersResult.ok) {
return mapErrorToResponse(commonHeadersResult.error, commonHeadersResult.correlationId, deps.logger);
}

try {
assertNotEmpty(event.headers, new Error('The request headers are empty'));
const lowerCasedHeaders = lowerCaseKeys(event.headers);
correlationId = assertNotEmpty(lowerCasedHeaders[deps.env.APIM_CORRELATION_HEADER],
new Error("The request headers don't contain the APIM correlation id"));
const supplierId = assertNotEmpty(lowerCasedHeaders[deps.env.SUPPLIER_ID_HEADER],
new ValidationError(ApiErrorDetail.InvalidRequestMissingSupplierId));
const letterId = assertNotEmpty( event.pathParameters?.id,
new ValidationError(ApiErrorDetail.InvalidRequestMissingLetterIdPathParameter));
const body = assertNotEmpty(event.body, new ValidationError(ApiErrorDetail.InvalidRequestMissingBody));
Expand All @@ -37,15 +35,15 @@ export function createPatchLetterHandler(deps: Deps): APIGatewayProxyHandler {
else throw error;
}

const result = await patchLetterStatus(mapToLetterDto(patchLetterRequest, supplierId), letterId, deps.letterRepo);
const updatedLetter = await patchLetterStatus(mapToLetterDto(patchLetterRequest, commonHeadersResult.value.supplierId), letterId, deps.letterRepo);

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

} catch (error) {
return mapErrorToResponse(error, correlationId, deps.logger);
return mapErrorToResponse(error, commonHeadersResult.value.correlationId, deps.logger);
}
};
};
34 changes: 34 additions & 0 deletions lambdas/api-handler/src/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { APIGatewayProxyEventHeaders } from "aws-lambda";
import { EnvVars } from "../config/env";
import { ValidationError } from "../errors";
import { ApiErrorDetail } from "../contracts/errors";
import { mapErrorToResponse } from "../mappers/error-mapper";
import { getLetterDataUrl } from "../services/letter-operations";
import { Deps } from "../config/deps";

export function assertNotEmpty<T>(
value: T | null | undefined,
error: Error
Expand All @@ -20,3 +28,29 @@ export function assertNotEmpty<T>(
export function lowerCaseKeys(obj: Record<string, any>): Record<string, any> {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k.toLowerCase(), v]));
}

export function validateCommonHeaders(headers: APIGatewayProxyEventHeaders, deps: Deps
): { ok: true; value: {correlationId: string, supplierId: string } } | { ok: false; error: Error; correlationId?: string } {

if (!headers || Object.keys(headers).length === 0) {
return { ok: false, error: new Error("The request headers are empty") };
}

const lowerCasedHeaders = lowerCaseKeys(headers);

const correlationId = lowerCasedHeaders[deps.env.APIM_CORRELATION_HEADER];
if (!correlationId) {
return { ok: false, error: new Error("The request headers don't contain the APIM correlation id") };
}

const supplierId = lowerCasedHeaders[deps.env.SUPPLIER_ID_HEADER];
if (!supplierId) {
return {
ok: false,
error: new ValidationError(ApiErrorDetail.InvalidRequestMissingSupplierId),
correlationId,
};
}

return { ok: true, value: { correlationId, supplierId } };
}
Loading