Skip to content

Commit 34017bd

Browse files
committed
CCM-13906 Urgent Letter Priority
1 parent b9ea08f commit 34017bd

3 files changed

Lines changed: 79 additions & 17 deletions

File tree

internal/datastore/src/__test__/letter-queue-repository.test.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import { LetterAlreadyExistsError } from "../letter-already-exists-error";
1212
import { createTestLogger } from "./logs";
1313
import { LetterDoesNotExistError } from "../letter-does-not-exist-error";
1414

15-
function createLetter(letterId = "letter1"): PendingLetterBase {
15+
function createLetter(
16+
overrides: Partial<PendingLetterBase> = {},
17+
): PendingLetterBase {
1618
return {
17-
letterId,
19+
letterId: "letter1",
1820
supplierId: "supplier1",
1921
specificationId: "specification1",
2022
groupId: "group1",
23+
priority: 10,
24+
...overrides,
2125
};
2226
}
2327

@@ -55,17 +59,42 @@ describe("LetterQueueRepository", () => {
5559
});
5660

5761
describe("putLetter", () => {
58-
it("adds a letter to the database", async () => {
62+
beforeEach(() => {
5963
jest.useFakeTimers().setSystemTime(new Date("2026-03-04T13:15:45.000Z"));
64+
});
6065

66+
it("adds a letter to the database", async () => {
6167
const pendingLetter =
6268
await letterQueueRepository.putLetter(createLetter());
6369

6470
expect(pendingLetter.queueTimestamp).toBe("2026-03-04T13:15:45.000Z");
6571
expect(pendingLetter.ttl).toBe(1_772_633_745);
72+
expect(pendingLetter.queueSortOrderSk).toBe(
73+
"10-2026-03-04T13:15:45.000Z",
74+
);
6675
expect(await letterExists(db, "supplier1", "letter1")).toBe(true);
6776
});
6877

78+
it("left-pads the priority with zeros in the sort key", async () => {
79+
const pendingLetter = await letterQueueRepository.putLetter(
80+
createLetter({ priority: 5 }),
81+
);
82+
83+
expect(pendingLetter.queueSortOrderSk).toBe(
84+
"05-2026-03-04T13:15:45.000Z",
85+
);
86+
});
87+
88+
it("defaults a missing priority to 10 in the sort key", async () => {
89+
const pendingLetter = await letterQueueRepository.putLetter(
90+
createLetter({ priority: undefined }),
91+
);
92+
93+
expect(pendingLetter.queueSortOrderSk).toBe(
94+
"10-2026-03-04T13:15:45.000Z",
95+
);
96+
});
97+
6998
it("throws LetterAlreadyExistsError when creating a letter which already exists", async () => {
7099
await letterQueueRepository.putLetter(createLetter());
71100

@@ -139,11 +168,21 @@ describe("LetterQueueRepository", () => {
139168
});
140169

141170
it("returns letters in timestamp order", async () => {
142-
await letterQueueRepository.putLetter(createLetter("first-letter"));
143-
await letterQueueRepository.putLetter(createLetter("second-letter"));
144-
await letterQueueRepository.putLetter(createLetter("third-letter"));
145-
await letterQueueRepository.putLetter(createLetter("fourth-letter"));
146-
await letterQueueRepository.putLetter(createLetter("fifth-letter"));
171+
await letterQueueRepository.putLetter(
172+
createLetter({ letterId: "first-letter" }),
173+
);
174+
await letterQueueRepository.putLetter(
175+
createLetter({ letterId: "second-letter" }),
176+
);
177+
await letterQueueRepository.putLetter(
178+
createLetter({ letterId: "third-letter" }),
179+
);
180+
await letterQueueRepository.putLetter(
181+
createLetter({ letterId: "fourth-letter" }),
182+
);
183+
await letterQueueRepository.putLetter(
184+
createLetter({ letterId: "fifth-letter" }),
185+
);
147186

148187
const letters = await letterQueueRepository.getLetters("supplier1", 5);
149188

@@ -155,10 +194,18 @@ describe("LetterQueueRepository", () => {
155194
});
156195

157196
it("limits results to the supplied number", async () => {
158-
await letterQueueRepository.putLetter(createLetter("first-letter"));
159-
await letterQueueRepository.putLetter(createLetter("second-letter"));
160-
await letterQueueRepository.putLetter(createLetter("third-letter"));
161-
await letterQueueRepository.putLetter(createLetter("fourth-letter"));
197+
await letterQueueRepository.putLetter(
198+
createLetter({ letterId: "first-letter" }),
199+
);
200+
await letterQueueRepository.putLetter(
201+
createLetter({ letterId: "second-letter" }),
202+
);
203+
await letterQueueRepository.putLetter(
204+
createLetter({ letterId: "third-letter" }),
205+
);
206+
await letterQueueRepository.putLetter(
207+
createLetter({ letterId: "fourth-letter" }),
208+
);
162209

163210
const letters = await letterQueueRepository.getLetters("supplier1", 3);
164211

@@ -179,6 +226,7 @@ describe("LetterQueueRepository", () => {
179226

180227
const letter = await getLetter(db, "supplier1", "letter1");
181228
expect(letter?.queueTimestamp).toBe("2026-03-04T13:15:45.000Z");
229+
expect(letter?.queueSortOrderSk).toBe("10-2026-03-04T13:15:45.000Z");
182230
});
183231

184232
it("does nothing when the letter does not exist", async () => {

internal/datastore/src/letter-queue-repository.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ export default class LetterQueueRepository {
2323
readonly config: LetterQueueRepositoryConfig,
2424
) {}
2525

26+
private readonly defaultPriority = 10;
27+
2628
async putLetter(
2729
insertPendingLetter: PendingLetterBase,
2830
): Promise<PendingLetter> {
31+
const priority = String(insertPendingLetter.priority ?? 10);
32+
// needs to be an ISO timestamp as Db sorts alphabetically
33+
const queueTimestamp = new Date().toISOString();
34+
const queueSortOrderSk = `${priority.padStart(2, "0")}-${queueTimestamp}`;
2935
const pendingLetter: PendingLetter = {
3036
...insertPendingLetter,
31-
// needs to be an ISO timestamp as Db sorts alphabetically
32-
queueTimestamp: new Date().toISOString(),
37+
queueTimestamp,
38+
queueSortOrderSk,
3339
ttl: Math.floor(
3440
Date.now() / 1000 + 60 * 60 * this.config.letterQueueTtlHours,
3541
),
@@ -102,6 +108,9 @@ export default class LetterQueueRepository {
102108
pendingLetter: PendingLetterBase,
103109
timestamp: Date,
104110
): Promise<void> {
111+
const priority = String(pendingLetter.priority ?? this.defaultPriority);
112+
const isoTimestamp = timestamp.toISOString();
113+
const queueSortOrderSk = `${priority.padStart(2, "0")}-${isoTimestamp}`;
105114
try {
106115
await this.ddbClient.send(
107116
new UpdateCommand({
@@ -110,10 +119,12 @@ export default class LetterQueueRepository {
110119
supplierId: pendingLetter.supplierId,
111120
letterId: pendingLetter.letterId,
112121
},
113-
UpdateExpression: "SET queueTimestamp = :ts",
122+
UpdateExpression:
123+
"SET queueTimestamp = :ts, queueSortOrderSk = :sortOrder",
114124
ConditionExpression: "attribute_exists(letterId)",
115125
ExpressionAttributeValues: {
116-
":ts": timestamp.toISOString(),
126+
":ts": isoTimestamp,
127+
":sortOrder": queueSortOrderSk,
117128
},
118129
}),
119130
);

internal/datastore/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const LetterSchemaBase = z.object({
4343
export const LetterSchema = LetterSchemaBase.extend({
4444
supplierId: idRef(SupplierSchema, "id"),
4545
eventId: z.string().optional(),
46+
priority: z.int().min(0).max(99).optional(), // A lower number represents a higher priority
4647
url: z.url(),
4748
createdAt: z.string(),
4849
updatedAt: z.string(),
@@ -81,12 +82,14 @@ export const PendingLetterSchemaBase = z.object({
8182
letterId: idRef(LetterSchema, "id"),
8283
specificationId: z.string(),
8384
groupId: z.string(),
85+
priority: z.int().min(0).max(99).optional(),
8486
});
8587

8688
export const PendingLetterSchema = z.object({
8789
supplierId: idRef(SupplierSchema, "id"),
8890
letterId: idRef(LetterSchema, "id"),
89-
queueTimestamp: z.string().describe("Secondary index SK"),
91+
queueTimestamp: z.string(),
92+
queueSortOrderSk: z.string().describe("Secondary index SK"),
9093
specificationId: z.string(),
9194
groupId: z.string(),
9295
ttl: z.int(),

0 commit comments

Comments
 (0)