Skip to content

Commit d66cd52

Browse files
authored
Setup automatic expiration policies (#265)
1 parent 7557300 commit d66cd52

File tree

7 files changed

+55
-9
lines changed

7 files changed

+55
-9
lines changed

src/api/functions/auditLog.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@ import {
55
} from "@aws-sdk/client-dynamodb";
66
import { marshall } from "@aws-sdk/util-dynamodb";
77
import { genericConfig } from "common/config.js";
8+
import { AUDIT_LOG_RETENTION_DAYS } from "common/constants.js";
89
import { AuditLogEntry } from "common/types/logs.js";
910

1011
type AuditLogParams = {
1112
dynamoClient?: DynamoDBClient;
1213
entry: AuditLogEntry;
1314
};
1415

15-
const RETENTION_DAYS = 365;
16-
1716
function buildMarshalledAuditLogItem(entry: AuditLogEntry) {
1817
const baseNow = Date.now();
1918
const timestamp = Math.floor(baseNow / 1000);
2019
const expireAt =
21-
timestamp + Math.floor((RETENTION_DAYS * 24 * 60 * 60 * 1000) / 1000);
20+
timestamp +
21+
Math.floor((AUDIT_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000) / 1000);
2222

2323
return marshall(
2424
{

src/api/routes/roomRequests.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
getDefaultFilteringQuerystring,
3737
nonEmptyCommaSeparatedStringSchema,
3838
} from "common/utils.js";
39+
import { ROOM_RESERVATION_RETENTION_DAYS } from "common/constants.js";
3940

4041
const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
4142
await fastify.register(rateLimiter, {
@@ -104,6 +105,9 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
104105
semesterId,
105106
"createdAt#status": `${createdAt}#${request.body.status}`,
106107
createdBy: request.username,
108+
expiresAt:
109+
Math.floor(Date.now() / 1000) +
110+
86400 * ROOM_RESERVATION_RETENTION_DAYS,
107111
...request.body,
108112
},
109113
{ removeUndefinedValues: true },
@@ -315,6 +319,9 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
315319
userId: request.username,
316320
"userId#requestId": `${request.username}#${requestId}`,
317321
semesterId: request.body.semester,
322+
expiresAt:
323+
Math.floor(Date.now() / 1000) +
324+
86400 * ROOM_RESERVATION_RETENTION_DAYS,
318325
};
319326
const logStatement = buildAuditLogTransactPut({
320327
entry: {
@@ -344,6 +351,9 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
344351
"createdAt#status": `${createdAt}#${RoomRequestStatus.CREATED}`,
345352
createdBy: request.username,
346353
status: RoomRequestStatus.CREATED,
354+
expiresAt:
355+
Math.floor(Date.now() / 1000) +
356+
86400 * ROOM_RESERVATION_RETENTION_DAYS,
347357
notes: "This request was created by the user.",
348358
}),
349359
},

src/api/routes/stripe.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { AvailableSQSFunctions, SQSPayload } from "common/types/sqsMessage.js";
4545
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
4646
import * as z from "zod/v4";
4747
import { getAllUserEmails } from "common/utils.js";
48+
import { STRIPE_LINK_RETENTION_DAYS } from "common/constants.js";
4849

4950
const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
5051
await fastify.register(rawbody, {
@@ -249,6 +250,9 @@ const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
249250
message: "Deactivated Stripe payment link",
250251
},
251252
});
253+
// expire deleted links at 90 days
254+
const expiresAt =
255+
Math.floor(Date.now() / 1000) + 86400 * STRIPE_LINK_RETENTION_DAYS;
252256
const dynamoCommand = new TransactWriteItemsCommand({
253257
TransactItems: [
254258
...(logStatement ? [logStatement] : []),
@@ -259,11 +263,12 @@ const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
259263
userId: { S: unmarshalledEntry.userId },
260264
linkId: { S: linkId },
261265
},
262-
UpdateExpression: "SET active = :new_val",
266+
UpdateExpression: "SET active = :new_val, expiresAt = :ttl",
263267
ConditionExpression: "active = :old_val",
264268
ExpressionAttributeValues: {
265269
":new_val": { BOOL: false },
266270
":old_val": { BOOL: true },
271+
":ttl": { N: expiresAt.toString() },
267272
},
268273
},
269274
},
@@ -666,11 +671,18 @@ Please contact Officer Board with any questions.`,
666671
userId: { S: unmarshalledEntry.userId },
667672
linkId: { S: paymentLinkId },
668673
},
669-
UpdateExpression: "SET active = :new_val",
674+
UpdateExpression:
675+
"SET active = :new_val, expiresAt = :ttl",
670676
ConditionExpression: "active = :old_val",
671677
ExpressionAttributeValues: {
672678
":new_val": { BOOL: false },
673679
":old_val": { BOOL: true },
680+
":ttl": {
681+
N: (
682+
Math.floor(Date.now() / 1000) +
683+
86400 * STRIPE_LINK_RETENTION_DAYS
684+
).toString(),
685+
},
674686
},
675687
},
676688
},

src/api/routes/tickets.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { createAuditLogEntry } from "api/functions/auditLog.js";
2626
import { Modules } from "common/modules.js";
2727
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
2828
import { withRoles, withTags } from "api/components/index.js";
29+
import { FULFILLED_PURCHASES_RETENTION_DAYS } from "common/constants.js";
2930

3031
const postMerchSchema = z.object({
3132
type: z.literal("merch"),
@@ -355,6 +356,9 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
355356
message: "Could not find username.",
356357
});
357358
}
359+
const expiresAt =
360+
Math.floor(Date.now() / 1000) +
361+
86400 * FULFILLED_PURCHASES_RETENTION_DAYS;
358362
switch (request.body.type) {
359363
case "merch":
360364
ticketId = request.body.stripePi;
@@ -363,7 +367,7 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
363367
Key: {
364368
stripe_pi: { S: ticketId },
365369
},
366-
UpdateExpression: "SET fulfilled = :true_val",
370+
UpdateExpression: "SET fulfilled = :true_val, expiresAt = :ttl",
367371
ConditionExpression:
368372
"#email = :email_val AND (attribute_not_exists(fulfilled) OR fulfilled = :false_val) AND (attribute_not_exists(refunded) OR refunded = :false_val)",
369373
ExpressionAttributeNames: {
@@ -373,6 +377,7 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
373377
":true_val": { BOOL: true },
374378
":false_val": { BOOL: false },
375379
":email_val": { S: request.body.email },
380+
":ttl": { N: expiresAt.toString() },
376381
},
377382
ReturnValuesOnConditionCheckFailure: "ALL_OLD",
378383
ReturnValues: "ALL_OLD",
@@ -385,7 +390,7 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
385390
Key: {
386391
ticket_id: { S: ticketId },
387392
},
388-
UpdateExpression: "SET #used = :trueValue",
393+
UpdateExpression: "SET #used = :trueValue, expiresAt = :ttl",
389394
ConditionExpression:
390395
"(attribute_not_exists(#used) OR #used = :falseValue) AND (attribute_not_exists(refunded) OR refunded = :falseValue)",
391396
ExpressionAttributeNames: {
@@ -394,6 +399,7 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
394399
ExpressionAttributeValues: {
395400
":trueValue": { BOOL: true },
396401
":falseValue": { BOOL: false },
402+
":ttl": { N: expiresAt.toString() },
397403
},
398404
ReturnValuesOnConditionCheckFailure: "ALL_OLD",
399405
ReturnValues: "ALL_OLD",

src/common/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const STRIPE_LINK_RETENTION_DAYS = 90; // this number of days after the link is deactivated.
2+
export const AUDIT_LOG_RETENTION_DAYS = 365;
3+
export const ROOM_RESERVATION_RETENTION_DAYS = 730;
4+
export const FULFILLED_PURCHASES_RETENTION_DAYS = 365; // ticketing/merch: after the purchase is marked as fulfilled.

src/ui/pages/stripe/CurrentLinks.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { notifications } from "@mantine/notifications";
1818
import { useAuth } from "@ui/components/AuthContext";
1919
import pluralize from "pluralize";
2020
import dayjs from "dayjs";
21+
import { STRIPE_LINK_RETENTION_DAYS } from "@common/constants";
2122

2223
const HumanFriendlyDate = ({ date }: { date: string | Date }) => {
2324
return <Text size="sm">{dayjs(date).format("MMMM D, YYYY")}</Text>;
@@ -146,7 +147,8 @@ export const StripeCurrentLinksPanel: React.FC<
146147
}
147148
if (result.success > 0) {
148149
notifications.show({
149-
message: `Deactivated ${pluralize("link", result.success, true)}!`,
150+
title: `Deactivated ${pluralize("link", result.success, true)}!`,
151+
message: `Links will be permanently removed from this page after ${STRIPE_LINK_RETENTION_DAYS} days.`,
150152
color: "green",
151153
});
152154
}

terraform/modules/dynamo/main.tf

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ resource "aws_dynamodb_table" "app_audit_log" {
1717
}
1818
ttl {
1919
attribute_name = "expiresAt"
20-
enabled = true
20+
enabled = false
2121
}
2222
}
2323

@@ -80,6 +80,10 @@ resource "aws_dynamodb_table" "room_requests" {
8080
hash_key = "requestId"
8181
projection_type = "ALL"
8282
}
83+
ttl {
84+
attribute_name = "expiresAt"
85+
enabled = true
86+
}
8387
}
8488

8589

@@ -110,6 +114,10 @@ resource "aws_dynamodb_table" "room_requests_status" {
110114
range_key = "requestId"
111115
projection_type = "ALL"
112116
}
117+
ttl {
118+
attribute_name = "expiresAt"
119+
enabled = true
120+
}
113121
}
114122

115123

@@ -224,6 +232,10 @@ resource "aws_dynamodb_table" "stripe_links" {
224232
hash_key = "linkId"
225233
projection_type = "ALL"
226234
}
235+
ttl {
236+
attribute_name = "expiresAt"
237+
enabled = true
238+
}
227239
}
228240

229241
resource "aws_dynamodb_table" "linkry_records" {

0 commit comments

Comments
 (0)