Skip to content
Draft
2 changes: 2 additions & 0 deletions internal/datastore/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const LetterSchemaBase = z.object({
groupId: z.string(),
reasonCode: z.string().optional(),
reasonText: z.string().optional(),
sha256Hash: z.string().optional(),
});

export const LetterSchema = LetterSchemaBase.extend({
Expand Down Expand Up @@ -87,6 +88,7 @@ export const PendingLetterSchemaBase = z.object({
letterId: idRef(LetterSchema, "id"),
specificationId: z.string(),
groupId: z.string(),
sha256Hash: z.string().optional(),
});

export const PendingLetterSchema = PendingLetterSchemaBase.extend({
Expand Down
1 change: 0 additions & 1 deletion internal/event-builders/src/letter-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export function mapLetterToCloudEvent(
dataschemaversion,
source,
subject: `letter-origin/letter-rendering/letter/${letter.id}`,

data: {
domainId: letter.id as LetterStatusChangeEvent["data"]["domainId"],
status: letter.status,
Expand Down
2 changes: 2 additions & 0 deletions lambdas/api-handler/src/contracts/letters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const GetLetterResponseResourceSchema = z
groupId: z.string().optional(),
reasonCode: z.string().optional(),
reasonText: z.string().optional(),
sha256Hash: z.string().optional(),
})
.strict(),
})
Expand All @@ -66,6 +67,7 @@ export const GetLettersResponseResourceSchema = z
status: LetterStatusSchema,
specificationId: z.string(),
groupId: z.string().optional(),
sha256Hash: z.string().optional(),
})
.strict(),
})
Expand Down
90 changes: 90 additions & 0 deletions lambdas/api-handler/src/mappers/__tests__/letter-mapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,43 @@ describe("letter-mapper", () => {
});
});

it("maps an internal Letter to a GetLetterResponse with sha256Hash when present", () => {
const date = new Date().toISOString();
const letter: Letter = {
id: "abc123",
status: "PENDING",
supplierId: "supplier1",
specificationId: "spec123",
billingRef: "spec123",
groupId: "group123",
url: "https://example.com/letter/abc123",
createdAt: date,
updatedAt: date,
supplierStatus: "supplier1#PENDING",
supplierStatusSk: date,
ttl: 123,
sha256Hash: "abc123hash",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: GetLetterResponse = mapToGetLetterResponse(letter);

expect(result).toEqual({
data: {
id: "abc123",
type: "Letter",
attributes: {
specificationId: "spec123",
status: "PENDING",
groupId: "group123",
sha256Hash: "abc123hash",
},
},
});
});

it("maps an internal Letter to a GetLetterResponse with reasonCode and reasonText when present", () => {
const date = new Date().toISOString();
const letter: Letter = {
Expand Down Expand Up @@ -209,4 +246,57 @@ describe("letter-mapper", () => {
],
});
});
it("maps an internal Letter collection to a GetLettersResponse with sha256Hash when present", () => {
const date = new Date().toISOString();
const letter: Letter = {
id: "abc123",
status: "PENDING",
supplierId: "supplier1",
specificationId: "spec123",
billingRef: "spec123",
groupId: "group123",
url: "https://example.com/letter/abc123",
createdAt: date,
updatedAt: date,
supplierStatus: "supplier1#PENDING",
supplierStatusSk: date,
ttl: 123,
reasonCode: "R01",
reasonText: "Reason text",
sha256Hash: "abc123hash",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: GetLettersResponse = mapToGetLettersResponse([
letter,
letter,
]);

expect(result).toEqual({
data: [
{
id: "abc123",
type: "Letter",
attributes: {
specificationId: "spec123",
status: "PENDING",
groupId: "group123",
sha256Hash: "abc123hash",
},
},
{
id: "abc123",
type: "Letter",
attributes: {
specificationId: "spec123",
status: "PENDING",
groupId: "group123",
sha256Hash: "abc123hash",
},
},
],
});
});
});
2 changes: 2 additions & 0 deletions lambdas/api-handler/src/mappers/letter-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function letterToResourceResponse(letter: LetterBase) {
groupId: letter.groupId,
...(letter.reasonCode != null && { reasonCode: letter.reasonCode }),
...(letter.reasonText != null && { reasonText: letter.reasonText }),
...(letter.sha256Hash != null && { sha256Hash: letter.sha256Hash }),
},
};
}
Expand All @@ -34,6 +35,7 @@ function letterToGetLettersResourceResponse(letter: LetterBase) {
status: letter.status,
specificationId: letter.specificationId,
groupId: letter.groupId,
...(letter.sha256Hash != null && { sha256Hash: letter.sha256Hash }),
},
};
}
Expand Down
1 change: 1 addition & 0 deletions lambdas/api-handler/src/services/letter-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function mapPendingLetterToLetterBase(pending: PendingLetterBase): LetterBase {
status: "PENDING",
specificationId: pending.specificationId,
groupId: pending.groupId,
sha256Hash: pending.sha256Hash,
};
}

Expand Down
1 change: 1 addition & 0 deletions lambdas/upsert-letter/src/handler/upsert-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ function mapToInsertLetter(
upsertRequest.data.templateId,
),
url: upsertRequest.data.url,
sha256Hash: upsertRequest.data.sha256Hash,
source: upsertRequest.source,
subject: upsertRequest.subject,
createdAt: now,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ describe("uploadFile", () => {
Key: `${supplierId}/${targetFilename}`,
Body: Buffer.from("fake-pdf-bytes"),
ContentType: "application/pdf",
Metadata: {
sha256Hash:
"50af8d443ccf8b2777b72a9169cd0665ef4be5335b8f53543556fa0d320b135b",
},
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import path from "node:path";

Expand All @@ -12,12 +13,16 @@ export default async function uploadFile(
const s3 = new S3Client();
const filePath = path.join(__dirname, "..", "test-letters", sourceFilename);
const fileContent = readFileSync(filePath);
const hash = createHash("sha256").update(fileContent).digest("hex");

const uploadParams = {
Bucket: bucketName,
Key: `${folder}/${targetFilename}`,
Body: fileContent,
ContentType: "application/pdf",
Metadata: {
sha256Hash: hash,
},
};

const command = new PutObjectCommand(uploadParams);
Expand Down
Loading