Skip to content

Commit d8be3ea

Browse files
committed
use a consistent caching policy
1 parent ab94d63 commit d8be3ea

File tree

1 file changed

+62
-46
lines changed

1 file changed

+62
-46
lines changed

src/api/routes/events.ts

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { IUpdateDiscord, updateDiscord } from "../functions/discord.js";
2828

2929
const repeatOptions = ["weekly", "biweekly"] as const;
3030
const EVENT_CACHE_SECONDS = 90;
31+
const CLIENT_HTTP_CACHE_POLICY =
32+
"public, max-age=180, stale-while-revalidate=420, stale-if-error=3600";
3133
export type EventRepeatOptions = (typeof repeatOptions)[number];
3234

3335
const baseSchema = z.object({
@@ -58,7 +60,7 @@ const postRequestSchema = requestSchema.refine(
5860
export type EventPostRequest = z.infer<typeof postRequestSchema>;
5961
type EventGetRequest = {
6062
Params: { id: string };
61-
Querystring: undefined;
63+
Querystring: { ts?: number };
6264
Body: undefined;
6365
};
6466

@@ -119,14 +121,15 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
119121
message: `${userProvidedId} is not a valid event ID.`,
120122
});
121123
}
124+
originalEvent = unmarshall(originalEvent);
122125
}
123126
const entry = {
124127
...request.body,
125128
id: entryUUID,
126-
createdBy: request.username,
127-
createdAt: originalEvent
128-
? originalEvent.createdAt || new Date().toISOString()
129-
: new Date().toISOString(),
129+
createdAt:
130+
originalEvent && originalEvent.createdAt
131+
? originalEvent.createdAt
132+
: new Date().toISOString(),
130133
updatedAt: new Date().toISOString(),
131134
};
132135
await fastify.dynamoClient.send(
@@ -173,6 +176,9 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
173176
}
174177
throw new DiscordEventError({});
175178
}
179+
fastify.nodeCache.del("events-upcoming_only=true");
180+
fastify.nodeCache.del("events-upcoming_only=false");
181+
fastify.nodeCache.del(`event-${entryUUID}`);
176182
reply.status(201).send({
177183
id: entryUUID,
178184
resource: `/api/v1/events/${entryUUID}`,
@@ -198,36 +204,47 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
198204
"/:id",
199205
{
200206
schema: {
207+
querystring: {
208+
type: "object",
209+
properties: {
210+
ts: { type: "number" },
211+
},
212+
},
201213
response: { 200: getEventJsonSchema },
202214
},
203215
},
204216
async (request: FastifyRequest<EventGetRequest>, reply) => {
205217
const id = request.params.id;
218+
const ts = request.query?.ts;
219+
const cachedResponse = fastify.nodeCache.get(`event-${id}`);
220+
if (cachedResponse) {
221+
if (!ts) {
222+
reply.header("Cache-Control", CLIENT_HTTP_CACHE_POLICY);
223+
}
224+
return reply.header("x-acm-cache-status", "hit").send(cachedResponse);
225+
}
226+
206227
try {
207228
const response = await fastify.dynamoClient.send(
208-
new QueryCommand({
229+
new GetItemCommand({
209230
TableName: genericConfig.EventsDynamoTableName,
210-
KeyConditionExpression: "#id = :id",
211-
ExpressionAttributeNames: {
212-
"#id": "id",
213-
},
214-
ExpressionAttributeValues: marshall({ ":id": id }),
231+
Key: marshall({ id }),
215232
}),
216233
);
217-
const items = response.Items?.map((item) => unmarshall(item));
218-
if (items?.length !== 1) {
219-
throw new NotFoundError({
220-
endpointName: request.url,
221-
});
234+
const item = response.Item ? unmarshall(response.Item) : null;
235+
if (!item) {
236+
throw new NotFoundError({ endpointName: request.url });
222237
}
223-
reply.send(items[0]);
224-
} catch (e: unknown) {
238+
fastify.nodeCache.set(`event-${id}`, item, EVENT_CACHE_SECONDS);
239+
240+
if (!ts) {
241+
reply.header("Cache-Control", CLIENT_HTTP_CACHE_POLICY);
242+
}
243+
return reply.header("x-acm-cache-status", "miss").send(item);
244+
} catch (e) {
225245
if (e instanceof BaseError) {
226246
throw e;
227247
}
228-
if (e instanceof Error) {
229-
request.log.error("Failed to get from DynamoDB: " + e.toString());
230-
}
231248
throw new DatabaseFetchError({
232249
message: "Failed to get event from Dynamo table.",
233250
});
@@ -268,6 +285,9 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
268285
id,
269286
resource: `/api/v1/events/${id}`,
270287
});
288+
fastify.nodeCache.del("events-upcoming_only=true");
289+
fastify.nodeCache.del("events-upcoming_only=false");
290+
fastify.nodeCache.del(`event-${id}`);
271291
} catch (e: unknown) {
272292
if (e instanceof Error) {
273293
request.log.error("Failed to delete from DynamoDB: " + e.toString());
@@ -306,18 +326,17 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
306326
const host = request.query?.host;
307327
const ts = request.query?.ts; // we only use this to disable cache control
308328
const cachedResponse = fastify.nodeCache.get(
309-
`events-upcoming_only=${upcomingOnly}|host=${host}`,
310-
);
329+
`events-upcoming_only=${upcomingOnly}`,
330+
) as EventsGetResponse;
311331
if (cachedResponse) {
312-
return reply
313-
.header(
314-
"cache-control",
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",
318-
)
319-
.header("x-acm-cache-status", "hit")
320-
.send(cachedResponse);
332+
if (!ts) {
333+
reply.header("Cache-Control", CLIENT_HTTP_CACHE_POLICY);
334+
}
335+
let filteredResponse = cachedResponse;
336+
if (host) {
337+
filteredResponse = cachedResponse.filter((x) => x["host"] == host);
338+
}
339+
return reply.header("x-acm-cache-status", "hit").send(filteredResponse);
321340
}
322341
try {
323342
let command;
@@ -371,20 +390,17 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
371390
}
372391
});
373392
}
374-
fastify.nodeCache.set(
375-
`events-upcoming_only=${upcomingOnly}`,
376-
parsedItems,
377-
EVENT_CACHE_SECONDS,
378-
);
379-
reply
380-
.header(
381-
"cache-control",
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",
385-
)
386-
.header("x-acm-cache-status", "miss")
387-
.send(parsedItems);
393+
if (!host) {
394+
fastify.nodeCache.set(
395+
`events-upcoming_only=${upcomingOnly}`,
396+
parsedItems,
397+
EVENT_CACHE_SECONDS,
398+
);
399+
}
400+
if (!ts) {
401+
reply.header("Cache-Control", CLIENT_HTTP_CACHE_POLICY);
402+
}
403+
return reply.header("x-acm-cache-status", "hit").send(parsedItems);
388404
} catch (e: unknown) {
389405
if (e instanceof Error) {
390406
request.log.error("Failed to get from DynamoDB: " + e.toString());

0 commit comments

Comments
 (0)