@@ -28,6 +28,8 @@ import { IUpdateDiscord, updateDiscord } from "../functions/discord.js";
28
28
29
29
const repeatOptions = [ "weekly" , "biweekly" ] as const ;
30
30
const EVENT_CACHE_SECONDS = 90 ;
31
+ const CLIENT_HTTP_CACHE_POLICY =
32
+ "public, max-age=180, stale-while-revalidate=420, stale-if-error=3600" ;
31
33
export type EventRepeatOptions = ( typeof repeatOptions ) [ number ] ;
32
34
33
35
const baseSchema = z . object ( {
@@ -58,7 +60,7 @@ const postRequestSchema = requestSchema.refine(
58
60
export type EventPostRequest = z . infer < typeof postRequestSchema > ;
59
61
type EventGetRequest = {
60
62
Params : { id : string } ;
61
- Querystring : undefined ;
63
+ Querystring : { ts ?: number } ;
62
64
Body : undefined ;
63
65
} ;
64
66
@@ -119,14 +121,15 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
119
121
message : `${ userProvidedId } is not a valid event ID.` ,
120
122
} ) ;
121
123
}
124
+ originalEvent = unmarshall ( originalEvent ) ;
122
125
}
123
126
const entry = {
124
127
...request . body ,
125
128
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 ( ) ,
130
133
updatedAt : new Date ( ) . toISOString ( ) ,
131
134
} ;
132
135
await fastify . dynamoClient . send (
@@ -173,6 +176,9 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
173
176
}
174
177
throw new DiscordEventError ( { } ) ;
175
178
}
179
+ fastify . nodeCache . del ( "events-upcoming_only=true" ) ;
180
+ fastify . nodeCache . del ( "events-upcoming_only=false" ) ;
181
+ fastify . nodeCache . del ( `event-${ entryUUID } ` ) ;
176
182
reply . status ( 201 ) . send ( {
177
183
id : entryUUID ,
178
184
resource : `/api/v1/events/${ entryUUID } ` ,
@@ -198,36 +204,47 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
198
204
"/:id" ,
199
205
{
200
206
schema : {
207
+ querystring : {
208
+ type : "object" ,
209
+ properties : {
210
+ ts : { type : "number" } ,
211
+ } ,
212
+ } ,
201
213
response : { 200 : getEventJsonSchema } ,
202
214
} ,
203
215
} ,
204
216
async ( request : FastifyRequest < EventGetRequest > , reply ) => {
205
217
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
+
206
227
try {
207
228
const response = await fastify . dynamoClient . send (
208
- new QueryCommand ( {
229
+ new GetItemCommand ( {
209
230
TableName : genericConfig . EventsDynamoTableName ,
210
- KeyConditionExpression : "#id = :id" ,
211
- ExpressionAttributeNames : {
212
- "#id" : "id" ,
213
- } ,
214
- ExpressionAttributeValues : marshall ( { ":id" : id } ) ,
231
+ Key : marshall ( { id } ) ,
215
232
} ) ,
216
233
) ;
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 } ) ;
222
237
}
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 ) {
225
245
if ( e instanceof BaseError ) {
226
246
throw e ;
227
247
}
228
- if ( e instanceof Error ) {
229
- request . log . error ( "Failed to get from DynamoDB: " + e . toString ( ) ) ;
230
- }
231
248
throw new DatabaseFetchError ( {
232
249
message : "Failed to get event from Dynamo table." ,
233
250
} ) ;
@@ -268,6 +285,9 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
268
285
id,
269
286
resource : `/api/v1/events/${ id } ` ,
270
287
} ) ;
288
+ fastify . nodeCache . del ( "events-upcoming_only=true" ) ;
289
+ fastify . nodeCache . del ( "events-upcoming_only=false" ) ;
290
+ fastify . nodeCache . del ( `event-${ id } ` ) ;
271
291
} catch ( e : unknown ) {
272
292
if ( e instanceof Error ) {
273
293
request . log . error ( "Failed to delete from DynamoDB: " + e . toString ( ) ) ;
@@ -306,18 +326,17 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
306
326
const host = request . query ?. host ;
307
327
const ts = request . query ?. ts ; // we only use this to disable cache control
308
328
const cachedResponse = fastify . nodeCache . get (
309
- `events-upcoming_only=${ upcomingOnly } |host= ${ host } ` ,
310
- ) ;
329
+ `events-upcoming_only=${ upcomingOnly } ` ,
330
+ ) as EventsGetResponse ;
311
331
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 ) ;
321
340
}
322
341
try {
323
342
let command ;
@@ -371,20 +390,17 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
371
390
}
372
391
} ) ;
373
392
}
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 ) ;
388
404
} catch ( e : unknown ) {
389
405
if ( e instanceof Error ) {
390
406
request . log . error ( "Failed to get from DynamoDB: " + e . toString ( ) ) ;
0 commit comments