Skip to content

Commit 6e4a97c

Browse files
authored
Support host filter for events (#74)
* support host filter for events * add host filtering live test
1 parent 807b1f7 commit 6e4a97c

File tree

4 files changed

+91
-10
lines changed

4 files changed

+91
-10
lines changed

src/api/routes/events.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ const getEventJsonSchema = zodToJsonSchema(getEventSchema);
7979

8080
const getEventsSchema = z.array(getEventSchema);
8181
export type EventsGetResponse = z.infer<typeof getEventsSchema>;
82-
type EventsGetQueryParams = { upcomingOnly?: boolean };
82+
type EventsGetQueryParams = {
83+
upcomingOnly?: boolean;
84+
host?: string;
85+
ts?: number;
86+
};
8387

8488
const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
8589
fastify.post<{ Body: EventPostRequest }>(
@@ -290,29 +294,50 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
290294
type: "object",
291295
properties: {
292296
upcomingOnly: { type: "boolean" },
297+
host: { type: "string" },
298+
ts: { type: "number" },
293299
},
294300
},
295301
response: { 200: getEventsSchema },
296302
},
297303
},
298304
async (request: FastifyRequest<EventsGetRequest>, reply) => {
299305
const upcomingOnly = request.query?.upcomingOnly || false;
306+
const host = request.query?.host;
307+
const ts = request.query?.ts; // we only use this to disable cache control
300308
const cachedResponse = fastify.nodeCache.get(
301-
`events-upcoming_only=${upcomingOnly}`,
309+
`events-upcoming_only=${upcomingOnly}|host=${host}`,
302310
);
303311
if (cachedResponse) {
304312
return reply
305313
.header(
306314
"cache-control",
307-
"public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
315+
ts
316+
? "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate"
317+
: "public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
308318
)
309-
.header("acm-cache-status", "hit")
319+
.header("x-acm-cache-status", "hit")
310320
.send(cachedResponse);
311321
}
312322
try {
313-
const response = await fastify.dynamoClient.send(
314-
new ScanCommand({ TableName: genericConfig.EventsDynamoTableName }),
315-
);
323+
let command;
324+
if (host) {
325+
command = new QueryCommand({
326+
TableName: genericConfig.EventsDynamoTableName,
327+
ExpressionAttributeValues: {
328+
":host": {
329+
S: host,
330+
},
331+
},
332+
KeyConditionExpression: "host = :host",
333+
IndexName: "HostIndex",
334+
});
335+
} else {
336+
command = new ScanCommand({
337+
TableName: genericConfig.EventsDynamoTableName,
338+
});
339+
}
340+
const response = await fastify.dynamoClient.send(command);
316341
const items = response.Items?.map((item) => unmarshall(item));
317342
const currentTimeChicago = moment().tz("America/Chicago");
318343
let parsedItems = getEventsSchema.parse(items);
@@ -354,9 +379,11 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
354379
reply
355380
.header(
356381
"cache-control",
357-
"public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
382+
ts
383+
? "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate"
384+
: "public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
358385
)
359-
.header("acm-cache-status", "miss")
386+
.header("x-acm-cache-status", "miss")
360387
.send(parsedItems);
361388
} catch (e: unknown) {
362389
if (e instanceof Error) {

tests/live/events.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ test("getting events", async () => {
1111
expect(responseJson.length).greaterThan(0);
1212
});
1313

14+
test("getting events for a given host", async () => {
15+
const response = await fetch(`${baseEndpoint}/api/v1/events?host=ACM`);
16+
expect(response.status).toBe(200);
17+
18+
const responseJson = (await response.json()) as EventsGetResponse;
19+
expect(responseJson.length).toBeGreaterThan(0);
20+
21+
responseJson.forEach((event) => {
22+
expect(event.host).toBe("ACM");
23+
});
24+
});
25+
1426
describe("Event lifecycle tests", async () => {
1527
let createdEventUuid;
1628
test("creating an event", { timeout: 30000 }, async () => {

tests/unit/events.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { afterAll, expect, test, beforeEach, vi } from "vitest";
2-
import { ScanCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
2+
import {
3+
ScanCommand,
4+
DynamoDBClient,
5+
QueryCommand,
6+
} from "@aws-sdk/client-dynamodb";
37
import { mockClient } from "aws-sdk-client-mock";
48
import init from "../../src/api/index.js";
59
import { EventGetResponse } from "../../src/api/routes/events.js";
610
import {
711
dynamoTableData,
812
dynamoTableDataUnmarshalled,
913
dynamoTableDataUnmarshalledUpcomingOnly,
14+
infraEventsOnly,
15+
infraEventsOnlyUnmarshalled,
1016
} from "./mockEventData.testdata.js";
1117
import { secretObject } from "./secret.testdata.js";
1218

@@ -58,6 +64,30 @@ test("Test upcoming only", async () => {
5864
expect(responseDataJson).toEqual(dynamoTableDataUnmarshalledUpcomingOnly);
5965
});
6066

67+
test("Test host filter", async () => {
68+
ddbMock.on(ScanCommand).rejects();
69+
ddbMock.on(QueryCommand).resolves({ Items: infraEventsOnly as any });
70+
const response = await app.inject({
71+
method: "GET",
72+
url: "/api/v1/events?host=Infrastructure Committee",
73+
});
74+
expect(response.statusCode).toBe(200);
75+
const responseDataJson = (await response.json()) as EventGetResponse;
76+
expect(responseDataJson).toEqual(infraEventsOnlyUnmarshalled);
77+
expect(ddbMock.commandCalls(QueryCommand)).toHaveLength(1);
78+
const queryCommandCall = ddbMock.commandCalls(QueryCommand)[0].args[0].input;
79+
expect(queryCommandCall).toEqual({
80+
TableName: "infra-core-api-events",
81+
ExpressionAttributeValues: {
82+
":host": {
83+
S: "Infrastructure Committee",
84+
},
85+
},
86+
KeyConditionExpression: "host = :host",
87+
IndexName: "HostIndex",
88+
});
89+
});
90+
6191
afterAll(async () => {
6292
await app.close();
6393
vi.useRealTimers();

tests/unit/mockEventData.testdata.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ const dynamoTableData = [
163163
},
164164
];
165165

166+
const infraEventsOnly = dynamoTableData.filter((x) => {
167+
return x["host"]["S"] === "Infrastructure Committee";
168+
});
169+
170+
const infraEventsOnlyUnmarshalled = infraEventsOnly.map((x: any) => {
171+
const temp = unmarshall(x);
172+
delete temp.createdBy;
173+
return temp;
174+
});
175+
166176
const dynamoTableDataUnmarshalled = dynamoTableData.map((x: any) => {
167177
const temp = unmarshall(x);
168178
delete temp.createdBy;
@@ -181,4 +191,6 @@ export {
181191
dynamoTableData,
182192
dynamoTableDataUnmarshalled,
183193
dynamoTableDataUnmarshalledUpcomingOnly,
194+
infraEventsOnly,
195+
infraEventsOnlyUnmarshalled,
184196
};

0 commit comments

Comments
 (0)