-
Notifications
You must be signed in to change notification settings - Fork 0
events-rsvp get all rsvps for an event route created #427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR introduces RSVP functionality by adding a new routing module with POST and GET endpoints for event RSVP management, registering it conditionally under the /rsvp path in development environments, and provisioning a new DynamoDB table with Global Secondary Index and multi-region replication to store RSVP data. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as API (POST/GET)
participant Auth as Auth Module
participant Membership as Membership Check
participant DynamoDB
rect rgb(240, 248, 255)
Note over Client,DynamoDB: POST /:orgId/event/:eventId
Client->>API: POST with UIUC token
API->>Auth: verifyUiucAccessToken
Auth-->>API: Extract netId
API->>Membership: checkPaidMembership
Membership->>DynamoDB: Query membership status
DynamoDB-->>Membership: Result
Membership-->>API: isPaidMember flag
Note over API: Constructs RSVP entry<br/>(Not persisted in current diff)
end
rect rgb(245, 245, 220)
Note over Client,DynamoDB: GET /:orgId/event/:eventId
Client->>API: GET with token
API->>DynamoDB: QueryCommand by eventId (EventIdIndex)
DynamoDB-->>API: RSVP items
API->>API: Unmarshal items
API-->>Client: Return RSVP list
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
💰 Infracost reportMonthly estimate generatedThis comment will be updated when code changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/api/routes/rsvp.ts (1)
3-3: Unused imports:withRolesandgetUserOrgRoles.These imports are not currently used. If authorization is added per the previous comment, they will be needed. Otherwise, remove them.
Also applies to: 6-6
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to data retention organization setting
📒 Files selected for processing (3)
src/api/index.ts(2 hunks)src/api/routes/rsvp.ts(1 hunks)terraform/modules/dynamo/main.tf(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/api/routes/rsvp.ts (5)
src/api/components/index.ts (1)
withTags(243-258)src/api/functions/uin.ts (1)
verifyUiucAccessToken(30-96)src/common/errors/index.ts (1)
ValidationError(99-108)src/api/functions/membership.ts (1)
checkPaidMembership(359-400)src/common/config.ts (1)
genericConfig(244-244)
🪛 ESLint
src/api/routes/rsvp.ts
[error] 1-1: Resolve error: EACCES: permission denied, open '/pMkMEsaHQW'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:233:12)
at checkFileExtension (/home/jailuser/git/node_modules/eslint-plugin-import/lib/rules/extensions.js:205:53)
at checkSourceValue (/home/jailuser/git/node_modules/eslint-module-utils/moduleVisitor.js:32:5)
(import/extensions)
[error] 2-2: Unexpected use of file extension "js" for "api/plugins/rateLimiter.js"
(import/extensions)
[error] 3-3: Unexpected use of file extension "js" for "api/components/index.js"
(import/extensions)
[error] 6-6: Unexpected use of file extension "js" for "api/functions/organizations.js"
(import/extensions)
[error] 11-11: Unexpected use of file extension "js" for "common/errors/index.js"
(import/extensions)
[error] 13-13: Unexpected use of file extension "js" for "api/functions/uin.js"
(import/extensions)
[error] 14-14: Unexpected use of file extension "js" for "api/functions/membership.js"
(import/extensions)
[error] 16-16: Unexpected use of file extension "js" for "common/config.js"
(import/extensions)
src/api/index.ts
[error] 65-65: Unexpected use of file extension "js" for "./routes/rsvp.js"
(import/extensions)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build Application
🔇 Additional comments (1)
src/api/index.ts (1)
65-65: LGTM!The import and conditional registration of
rsvpRoutesfollows the existing patterns in the codebase. The dev-only gate is consistent with howvendingPluginis registered.Also applies to: 381-383
| params: z.object({ | ||
| eventId: z.string().min(1).meta({ | ||
| description: "The previously-created event ID in the events API.", | ||
| }), | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing orgId in POST params schema.
The route path is /:orgId/event/:eventId but the params schema only defines eventId. This will cause validation issues and request.params.orgId won't be properly typed.
Apply this diff:
params: z.object({
+ orgId: z.string().min(1).meta({
+ description: "The organization ID the event belongs to.",
+ }),
eventId: z.string().min(1).meta({
description: "The previously-created event ID in the events API.",
}),
}),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| params: z.object({ | |
| eventId: z.string().min(1).meta({ | |
| description: "The previously-created event ID in the events API.", | |
| }), | |
| }), | |
| params: z.object({ | |
| orgId: z.string().min(1).meta({ | |
| description: "The organization ID the event belongs to.", | |
| }), | |
| eventId: z.string().min(1).meta({ | |
| description: "The previously-created event ID in the events API.", | |
| }), | |
| }), |
🤖 Prompt for AI Agents
In src/api/routes/rsvp.ts around lines 37 to 41, the params Zod schema only
includes eventId but the route path is /:orgId/event/:eventId, so orgId is
missing and request.params.orgId will not be validated or typed; update the
params schema to include orgId: z.string().min(1).meta({ description: "The
organization ID from the route." }) alongside eventId (keep the existing meta
for eventId) so that both route params are validated and typed correctly.
| "x-uiuc-token": z.jwt().min(1).meta({ | ||
| description: | ||
| "An access token for the user in the UIUC Entra ID tenant.", | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# Search for z.jwt() usage and any custom JWT schema definitions
rg -n "z\.jwt|\.jwt\(\)" --type ts -C 3Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
src/api/routes/syncIdentity.ts-64- {
src/api/routes/syncIdentity.ts-65- schema: withTags(["Generic"], {
src/api/routes/syncIdentity.ts-66- headers: z.object({
src/api/routes/syncIdentity.ts:67: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/syncIdentity.ts-68- description:
src/api/routes/syncIdentity.ts-69- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/syncIdentity.ts-70- }),
--
src/api/routes/syncIdentity.ts-150- {
src/api/routes/syncIdentity.ts-151- schema: withTags(["Generic"], {
src/api/routes/syncIdentity.ts-152- headers: z.object({
src/api/routes/syncIdentity.ts:153: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/syncIdentity.ts-154- description:
src/api/routes/syncIdentity.ts-155- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/syncIdentity.ts-156- }),
--
src/api/routes/rsvp.ts-40- }),
src/api/routes/rsvp.ts-41- }),
src/api/routes/rsvp.ts-42- headers: z.object({
src/api/routes/rsvp.ts:43: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/rsvp.ts-44- description:
src/api/routes/rsvp.ts-45- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/rsvp.ts-46- }),
--
src/api/routes/rsvp.ts-92- }),
src/api/routes/rsvp.ts-93- }),
src/api/routes/rsvp.ts-94- headers: z.object({
src/api/routes/rsvp.ts:95: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/rsvp.ts-96- description:
src/api/routes/rsvp.ts-97- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/rsvp.ts-98- }),
--
src/api/routes/v2/mobileWallet.ts-27- schema: withTags(["Mobile Wallet"], {
src/api/routes/v2/mobileWallet.ts-28- summary: "Retrieve mobile wallet pass for ACM member.",
src/api/routes/v2/mobileWallet.ts-29- headers: z.object({
src/api/routes/v2/mobileWallet.ts:30: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/v2/mobileWallet.ts-31- description:
src/api/routes/v2/mobileWallet.ts-32- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/v2/mobileWallet.ts-33- }),
--
src/api/routes/v2/membership.ts-77- {
src/api/routes/v2/membership.ts-78- schema: withTags(["Membership"], {
src/api/routes/v2/membership.ts-79- headers: z.object({
src/api/routes/v2/membership.ts:80: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/v2/membership.ts-81- description:
src/api/routes/v2/membership.ts-82- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/v2/membership.ts-83- }),
--
src/api/routes/membership.ts-45- {
src/api/routes/membership.ts-46- schema: withTags(["Membership"], {
src/api/routes/membership.ts-47- headers: z.object({
src/api/routes/membership.ts:48: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/membership.ts-49- description:
src/api/routes/membership.ts-50- "An access token for the user in the UIUC Entra ID tenant.",
src/api/routes/membership.ts-51- }),
</shell_result>
Script executed:
#!/bin/bash
# Check for Zod version in package.json and any custom extensions
cat package.json | grep -A2 -B2 "zod"Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 1
</shell_result>
Script executed:
#!/bin/bash
# Read the specific file to see context around lines 43-46 and 95-98
head -100 src/api/routes/rsvp.ts | tail -60Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
}),
headers: z.object({
"x-uiuc-token": z.jwt().min(1).meta({
description:
"An access token for the user in the UIUC Entra ID tenant.",
}),
}),
}),
},
async (request, reply) => {
const accessToken = request.headers["x-uiuc-token"];
const verifiedData = await verifyUiucAccessToken({
accessToken,
logger: request.log,
});
const { userPrincipalName: upn, givenName, surname } = verifiedData;
const netId = upn.replace("@illinois.edu", "");
if (netId.includes("@")) {
request.log.error(
`Found UPN ${upn} which cannot be turned into NetID via simple replacement.`,
);
throw new ValidationError({
message: "ID token could not be parsed.",
});
}
const isPaidMember = await checkPaidMembership({
netId,
dynamoClient: fastify.dynamoClient,
redisClient: fastify.redisClient,
logger: request.log,
});
const entry = {
partitionKey: `${request.params.eventId}#${upn}`,
eventId: request.params.eventId,
userId: upn,
isPaidMember,
createdAt: "",
};
},
);
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().get(
"/:orgId/event/:eventId",
{
schema: withTags(["RSVP"], {
summary: "Get all RSVPs for an event.",
params: z.object({
eventId: z.string().min(1).meta({
description: "The previously-created event ID in the events API.",
}),
orgId: z.string().min(1).meta({
description: "The organization ID the event belongs to.",
}),
}),
headers: z.object({
"x-uiuc-token": z.jwt().min(1).meta({
description:
"An access token for the user in the UIUC Entra ID tenant.",
}),
}),
}),
</shell_result>
Script executed:
#!/bin/bash
# Search for any zod extensions or custom schema definitions
rg -n "z\.|extend\(|.jwt" src/api/routes/rsvp.ts -C 2Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
16-import { genericConfig } from "common/config.js";
17-
18:const rsvpItemSchema = z.object({
19: eventId: z.string(),
20: userId: z.string(),
21: isPaidMember: z.boolean(),
22: createdAt: z.string(),
23-});
24:const rsvpListSchema = z.array(rsvpItemSchema);
25-
26-const rsvpRoutes: FastifyPluginAsync = async (fastify, _options) => {
--
35- schema: withTags(["RSVP"], {
36- summary: "Submit an RSVP for an event.",
37: params: z.object({
38: eventId: z.string().min(1).meta({
39- description: "The previously-created event ID in the events API.",
40- }),
41- }),
42: headers: z.object({
43: "x-uiuc-token": z.jwt().min(1).meta({
44- description:
45- "An access token for the user in the UIUC Entra ID tenant.",
--
84- schema: withTags(["RSVP"], {
85- summary: "Get all RSVPs for an event.",
86: params: z.object({
87: eventId: z.string().min(1).meta({
88- description: "The previously-created event ID in the events API.",
89- }),
90: orgId: z.string().min(1).meta({
91- description: "The organization ID the event belongs to.",
92- }),
93- }),
94: headers: z.object({
95: "x-uiuc-token": z.jwt().min(1).meta({
96- description:
97- "An access token for the user in the UIUC Entra ID tenant.",
--
111- const response = await fastify.dynamoClient.send(commnand);
112- const items = response.Items?.map((item) => unmarshall(item)) || [];
113: return reply.send(items as z.infer<typeof rsvpListSchema>);
114- },
115- );
</shell_result>
Script executed:
#!/bin/bash
# Search for jwt function definition or Zod extension
rg -n "jwt\s*[:=]|\.jwt\s*=|extend.*jwt" --type tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 1
</shell_result>
Script executed:
#!/bin/bash
# Check imports in rsvp.ts to understand where z comes from
head -20 src/api/routes/rsvp.tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
import { FastifyPluginAsync } from "fastify";
import rateLimiter from "api/plugins/rateLimiter.js";
import { withRoles, withTags } from "api/components/index.js";
import { QueryCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
import { getUserOrgRoles } from "api/functions/organizations.js";
import {
UnauthenticatedError,
UnauthorizedError,
ValidationError,
} from "common/errors/index.js";
import * as z from "zod/v4";
import { verifyUiucAccessToken } from "api/functions/uin.js";
import { checkPaidMembership } from "api/functions/membership.js";
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
import { genericConfig } from "common/config.js";
const rsvpItemSchema = z.object({
eventId: z.string(),
userId: z.string(),
</shell_result>
Script executed:
#!/bin/bash
# Look for zod configuration or extensions files
fd -e ts -e js | xargs rg -l "z\.extend|ZodType.*jwt" | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
</shell_result>
Script executed:
#!/bin/bash
# Check package.json for zod version and look at dependencies
cat package.json | head -50Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
{
"name": "infra-core",
"version": "1.0.0",
"private": true,
"type": "module",
"workspaces": [
"src/api",
"src/ui",
"src/archival",
"src/linkryEdgeFunction"
],
"packageManager": "yarn@1.22.22",
"scripts": {
"postinstall": "npm run setup",
"setup": "git config blame.ignoreRevsFile .git-blame-ignore-revs",
"build": "concurrently --names 'api,ui,archival,linkryEdge' 'yarn workspace infra-core-api run build' 'yarn workspace infra-core-ui run build' 'yarn workspace infra-core-archival run build' 'yarn workspace infra-core-linkry-edge run build'",
"postbuild": "node src/api/createLambdaPackage.js && yarn lockfile-manage",
"dev": "cross-env DISABLE_AUDIT_LOG=true concurrently --names 'api,ui' 'yarn workspace infra-core-api run dev' 'yarn workspace infra-core-ui run dev'",
"lockfile-manage": "cp src/api/package.lambda.json dist/lambda/package.json && cp src/api/package.lambda.json dist/sqsConsumer/package.json && cp yarn.lock dist/lambda/ && cp yarn.lock dist/sqsConsumer/",
"prettier": "yarn workspaces run prettier && prettier --check tests/**/*.ts",
"prettier:write": "yarn workspaces run prettier:write && prettier --write tests/**/*.ts",
"lint": "yarn workspaces run lint",
"prepare": "node .husky/install.mjs || true",
"typecheck": "yarn workspaces run typecheck",
"test:unit": "cross-env RunEnvironment='dev' concurrently --names 'api,ui' 'yarn run test:unit-api' 'yarn workspace infra-core-ui run test:unit'",
"test:unit-api": "cross-env RunEnvironment='dev' vitest run --coverage --config tests/unit/vitest.config.ts tests/unit",
"test:unit-ui": "yarn test:unit --ui",
"test:unit-watch": "vitest tests/unit",
"test:live": "vitest tests/live",
"test:live-ui": "yarn test:live --ui",
"test:e2e": "playwright test",
"test:e2e-ui": "playwright test --ui"
},
"dependencies": {
"@acm-uiuc/js-shared": "^3.2.1"
},
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.33.0",
"@playwright/test": "^1.56.1",
"@smithy/types": "^4.5.0",
"@tsconfig/node22": "^22.0.1",
"@types/ioredis-mock": "^8.2.5",
"@types/node": "^24.3.0",
"@types/pluralize": "^0.0.33",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^8.40.0",
</shell_result>
Script executed:
#!/bin/bash
# Search for any z.jwt or jwt extension setup
rg -n "jwt" --type ts -g "!node_modules" | grep -i "extend\|z\.jwt\|define" | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
src/api/routes/rsvp.ts:43: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/rsvp.ts:95: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/v2/membership.ts:80: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/v2/mobileWallet.ts:30: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/syncIdentity.ts:67: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/syncIdentity.ts:153: "x-uiuc-token": z.jwt().min(1).meta({
src/api/routes/membership.ts:48: "x-uiuc-token": z.jwt().min(1).meta({
</shell_result>
Script executed:
#!/bin/bash
# Look for zod setup or configuration files
fd -type f \( -name "*zod*" -o -name "*config*" \) --type ts --type js | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stderr:
error: 'ype' isn't a valid value for '--type <filetype>'
[possible values: file, directory, symlink, executable, empty, socket, pipe]
For more information try '--help'
</shell_result>
Script executed:
#!/bin/bash
# Search in common directory for any Zod extensions
rg -n "z\.|jwt" src/common --type ts -C 2 | head -50Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
src/common/utils.ts-38-
src/common/utils.ts-39-
src/common/utils.ts:40:export const nonEmptyCommaSeparatedStringSchema = z.
src/common/utils.ts:41: array(z.string().min(1)).
src/common/utils.ts-42- min(1, { message: "Filter expression must select at least one item." }).
src/common/utils.ts-43- transform((val) => val.map((item) => item.trim()))
--
src/common/utils.ts-48-export const getDefaultFilteringQuerystring = ({ defaultSelect }: GetDefaultFilteringQuerystringInput) => {
src/common/utils.ts-49- return {
src/common/utils.ts:50: select: z.optional(nonEmptyCommaSeparatedStringSchema).default(defaultSelect).meta({
src/common/utils.ts-51- description: "A list of attributes to return.",
src/common/utils.ts-52- ...(defaultSelect.length === 0 ? { default: ["<ALL ATTRIBUTES>"] } : { example: defaultSelect })
--
src/common/types/user.ts-2-import { illinoisUin } from "./generic.js";
src/common/types/user.ts-3-
src/common/types/user.ts:4:export const searchUserByUinRequest = z.object({
src/common/types/user.ts-5- uin: illinoisUin
src/common/types/user.ts-6-});
src/common/types/user.ts-7-
src/common/types/user.ts:8:export const searchUserByUinResponse = z.object({
src/common/types/user.ts:9: email: z.email(),
src/common/types/user.ts-10-});
--
src/common/types/tickets.ts-1-import * as z from "zod/v4";
src/common/types/tickets.ts:2:export const postMetadataSchema = z.object({
src/common/types/tickets.ts:3: type: z.union([z.literal("merch"), z.literal("ticket")]),
src/common/types/tickets.ts:4: itemSalesActive: z.union([z.date(), z.boolean()])
src/common/types/tickets.ts-5-});
src/common/types/tickets.ts-6-
src/common/types/tickets.ts:7:export type ItemPostData = z.infer<typeof postMetadataSchema>;
--
src/common/types/stripe.ts-1-import * as z from "zod/v4";
src/common/types/stripe.ts-2-
src/common/types/stripe.ts:3:export const invoiceLinkPostResponseSchema = z.object({
src/common/types/stripe.ts:4: id: z.string().min(1),
src/common/types/stripe.ts:5: link: z.string().url()
src/common/types/stripe.ts-6-});
src/common/types/stripe.ts-7-
src/common/types/stripe.ts:8:export const invoiceLinkPostRequestSchema = z.object({
src/common/types/stripe.ts:9: invoiceId: z.string().min(1),
src/common/types/stripe.ts:10: invoiceAmountUsd: z.number().min(50),
src/common/types/stripe.ts:11: contactName: z.string().min(1),
src/common/types/stripe.ts:12: contactEmail: z.string().email(),
src/common/types/stripe.ts:13: achPaymentsEnabled: z.optional(z.boolean()).default(false),
src/common/types/stripe.ts-14-});
src/common/types/stripe.ts-15-
src/common/types/stripe.ts:16:export type PostInvoiceLinkRequest = z.infer<
src/common/types/stripe.ts-17- typeof invoiceLinkPostRequestSchema>;
src/common/types/stripe.ts-18-
src/common/types/stripe.ts-19-
</shell_result>
Script executed:
#!/bin/bash
# Check API workspace for zod version
cat src/api/package.json | grep -A 2 -B 2 "zod"Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
"fastify-plugin": "^5.0.1",
"fastify-raw-body": "^5.0.0",
"fastify-zod-openapi": "^5.0.1",
"handlebars": "^4.7.8",
"ical-generator": "^10.0.0",
--
"stripe": "^19.2.0",
"uuid": "^13.0.0",
"zod": "^4.0.14",
"zod-validation-error": "^4.0.1"
},
"devDependencies": {
</shell_result>
Script executed:
#!/bin/bash
# Search for where jwt() might be extended onto z
rg -n "\.jwt\s*=" --type tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 1
</shell_result>
Script executed:
#!/bin/bash
# Look for any custom Zod types or extensions
rg -n "extend\|\.jwt\|jwt\(" --type ts | grep -v "x-uiuc-token" | head -30Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
</shell_result>
Script executed:
#!/bin/bash
# Check if zod has jwt method in v4
web_search "Zod v4 jwt method"Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 127
stderr:
/bin/bash: line 3: web_search: command not found
</shell_result>
Script executed:
#!/bin/bash
# Look for .d.ts files or type augmentation
find . -name "*.d.ts" -o -name "*types*" | grep -v node_modules | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
./src/api/resources/types.d.ts
./src/api/types.d.ts
./src/common/types
./src/ui/types.d.ts
./src/ui/vite-env.d.ts
</shell_result>
Script executed:
#!/bin/bash
# Search for any Zod augmentation or type declaration
rg -n "declare|augment|namespace.*z|interface.*z" --type ts | grep -i zod | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
</shell_result>
Script executed:
#!/bin/bash
# Check if there's a tsconfig that might reveal anything
cat tsconfig.json | head -50Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stderr:
cat: tsconfig.json: No such file or directory
</shell_result>
Script executed:
#!/bin/bash
# More thorough search for "jwt" across all files
rg -n "jwt" --type ts -C 1 | head -50Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
tests/unit/tickets.test.ts-31-const ddbMock = mockClient(DynamoDBClient);
tests/unit/tickets.test.ts:32:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/tickets.test.ts:33:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/tickets.test.ts-34-
--
tests/unit/secret.testdata.ts-18-const testSecretObject = {
tests/unit/secret.testdata.ts:19: jwt_key: "somethingreallysecret",
tests/unit/secret.testdata.ts-20-};
--
tests/unit/secret.testdata.ts-28-
tests/unit/secret.testdata.ts:29:const jwtPayload = {
tests/unit/secret.testdata.ts:30: aud: "custom_jwt",
tests/unit/secret.testdata.ts:31: iss: "custom_jwt",
tests/unit/secret.testdata.ts-32- iat: Math.floor(Date.now() / 1000),
--
tests/unit/secret.testdata.ts-54-
tests/unit/secret.testdata.ts:55:const jwtPayloadNoGroups = {
tests/unit/secret.testdata.ts:56: aud: "custom_jwt",
tests/unit/secret.testdata.ts:57: iss: "custom_jwt",
tests/unit/secret.testdata.ts-58- iat: Math.floor(Date.now() / 1000),
--
tests/unit/secret.testdata.ts-85- testSecretObject,
tests/unit/secret.testdata.ts:86: jwtPayload,
tests/unit/secret.testdata.ts:87: jwtPayloadNoGroups,
tests/unit/secret.testdata.ts-88- uinSecretJson,
--
tests/unit/logs.test.ts-13-const ddbMock = mockClient(DynamoDBClient);
tests/unit/logs.test.ts:14:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/logs.test.ts:15:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/logs.test.ts-16-
--
tests/unit/linkry.test.ts-17-const ddbMock = mockClient(DynamoDBClient);
tests/unit/linkry.test.ts:18:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/linkry.test.ts:19:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/linkry.test.ts-20-
--
tests/unit/ical.test.ts-12-const ddbMock = mockClient(DynamoDBClient);
tests/unit/ical.test.ts:13:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/ical.test.ts:14:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/ical.test.ts-15-
--
tests/unit/events.test.ts-15-const ddbMock = mockClient(DynamoDBClient);
tests/unit/events.test.ts:16:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/events.test.ts:17:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/events.test.ts-18-
--
tests/unit/eventPost.test.ts-19-const ddbMock = mockClient(DynamoDBClient);
tests/unit/eventPost.test.ts:20:const jwt_secret = testSecretObject["jwt_key"];
tests/unit/eventPost.test.ts:21:vi.stubEnv("JwtSigningKey", jwt_secret);
tests/unit/eventPost.test.ts-22-
</shell_result>
Script executed:
#!/bin/bash
# Read the API types file which likely contains the jwt() extension
cat src/api/types.d.tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
/* eslint-disable @typescript-eslint/no-unused-vars */
import { FastifyRequest, FastifyInstance, FastifyReply } from "fastify";
import { AppRoles, RunEnvironment } from "../common/roles.js";
import { AadToken } from "./plugins/auth.js";
import { ConfigType, SecretConfig, SecretTesting } from "../common/config.js";
import NodeCache from "node-cache";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import { SQSClient } from "@aws-sdk/client-sqs";
import { AvailableAuthorizationPolicy } from "common/policies/definition.js";
import type RedisModule from "ioredis";
import { type S3Client } from "@aws-sdk/client-s3";
export type Redis = RedisModule.default;
export type ValidLoggers = FastifyBaseLogger | pino.Logger;
interface CloudfrontLocation {
country: string | undefined;
city: string | undefined;
region: string | undefined;
latitude: string | undefined;
longitude: string | undefined;
postalCode: string | undefined;
}
declare module "fastify" {
interface FastifyInstance {
authenticate: (
request: FastifyRequest,
reply: FastifyReply,
) => Promise<void>;
authorize: (
request: FastifyRequest,
reply: FastifyReply,
validRoles: AppRoles[],
disableApiKeyAuth: boolean,
) => Promise<Set<AppRoles>>;
authorizeFromSchema: (
request: FastifyRequest,
reply: FastifyReply,
) => Promise<void>;
runEnvironment: RunEnvironment;
environmentConfig: ConfigType;
nodeCache: NodeCache;
dynamoClient: DynamoDBClient;
sqsClient?: SQSClient;
s3Client?: S3Client;
redisClient: Redis;
secretsManagerClient: SecretsManagerClient;
secretConfig: SecretConfig | (SecretConfig & SecretTesting);
refreshSecretConfig: CallableFunction;
}
interface FastifyRequest {
startTime: number;
username?: string;
userRoles?: Set<AppRoles>;
tokenPayload?: AadToken;
policyRestrictions?: AvailableAuthorizationPolicy[];
location: CloudfrontLocation;
}
}
export type NoDataRequest = {
Params: undefined;
Querystring: undefined;
Body: undefined;
};
</shell_result>
Script executed:
#!/bin/bash
# Also check the resources types
cat src/api/resources/types.d.tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module "*.png" {
const value: string;
export default value;
}
declare module "*.json" {
const value: Record<string, any>;
export default value;
}
</shell_result>
Script executed:
#!/bin/bash
# Search for fastify-zod-openapi imports and usage
rg -n "fastify-zod-openapi" --type tsRepository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
src/api/components/index.ts:2:import { FastifyZodOpenApiSchema } from "fastify-zod-openapi";
src/api/index.ts:24:} from "fastify-zod-openapi";
src/api/routes/ics.ts:24:} from "fastify-zod-openapi";
src/api/routes/logs.ts:12:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/membership.ts:23:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/organizations.ts:12:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/mobileWallet.ts:15:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/rsvp.ts:15:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/roomRequests.ts:29:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/vending.ts:3:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/user.ts:16:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/syncIdentity.ts:8:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/tickets.ts:28:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/stripe.ts:42:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/iam.ts:46:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/linkry.ts:47:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/v2/mobileWallet.ts:12:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/v2/membership.ts:18:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/apiKey.ts:5:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/plugins/authorizeFromSchema.ts:4:import { FastifyZodOpenApiSchema } from "fastify-zod-openapi";
src/api/routes/events.ts:44:} from "fastify-zod-openapi";
</shell_result>
Script executed:
#!/bin/bash
# Check if there's any setup file that might extend Zod
rg -n "import.*z|from.*zod" src/api --type ts | head -20Repository: acm-uiuc/core
<shell_result>
Repository: acm-uiuc/core
Exit code: 0
stdout:
src/api/index.ts:24:} from "fastify-zod-openapi";
src/api/index.ts:25:import { type ZodOpenApiVersion } from "zod-openapi";
src/api/index.ts:39:import authorizeFromSchemaPlugin from "./plugins/authorizeFromSchema.js";
src/api/index.ts:44:import organizationsPlugin from "./routes/organizations.js";
src/api/types.d.ts:10:import { AvailableAuthorizationPolicy } from "common/policies/definition.js";
src/api/routes/mobileWallet.ts:6:import * as z from "zod/v4";
src/api/routes/mobileWallet.ts:15:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/logs.ts:12:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/logs.ts:13:import * as z from "zod/v4";
src/api/routes/tickets.ts:2:import * as z from "zod/v4";
src/api/routes/tickets.ts:28:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/membership.ts:23:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/membership.ts:24:import * as z from "zod/v4";
src/api/routes/vending.ts:3:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/vending.ts:4:import * as z from "zod/v4";
src/api/routes/rsvp.ts:6:import { getUserOrgRoles } from "api/functions/organizations.js";
src/api/routes/rsvp.ts:12:import * as z from "zod/v4";
src/api/routes/rsvp.ts:15:import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
src/api/routes/user.ts:4:import { getUserOrgRoles } from "api/functions/organizations.js";
src/api/routes/user.ts:10:import * as z from "zod/v4";
</shell_result>
Zod v4 jwt method
<web_search_result>
Short answer: Zod v4 adds a string validator for JWTs: use z.string().jwt() (optionally z.string().jwt({ alg: "RS256" }) to require a specific alg). It only checks the three-part/base64url structure (and decodes header/payload), it does NOT verify signatures — use a JWT library (e.g., jsonwebtoken or jose) for cryptographic verification. [1][2]
</web_search_result>
Fix z.jwt() to z.string().jwt() — z.jwt() is not a valid Zod v4 method.
Zod v4 provides z.string().jwt() to validate JWT strings, not z.jwt(). The current code will fail at runtime. Replace z.jwt().min(1) with z.string().jwt().min(1).
Note: z.string().jwt() validates the JWT structure (three-part base64url format) but does not verify signatures—use a JWT library like jose for cryptographic verification.
🤖 Prompt for AI Agents
In src/api/routes/rsvp.ts around lines 43-46, replace the invalid Zod call
z.jwt().min(1) with z.string().jwt().min(1) so the header schema uses
z.string().jwt().min(1) to validate JWT-formatted strings; ensure imports still
reference z from zod and keep min(1) in the chain (or move before jwt() if
preferred).
| const entry = { | ||
| partitionKey: `${request.params.eventId}#${upn}`, | ||
| eventId: request.params.eventId, | ||
| userId: upn, | ||
| isPaidMember, | ||
| createdAt: "", | ||
| }; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POST handler is incomplete — entry is created but never persisted or returned.
The handler constructs an RSVP entry object but:
- Never writes it to DynamoDB
- Never sends a response to the client
- Sets
createdAtto an empty string instead of a timestamp
The handler needs to persist the entry and return a response. Example fix:
const entry = {
partitionKey: `${request.params.eventId}#${upn}`,
eventId: request.params.eventId,
userId: upn,
isPaidMember,
- createdAt: "",
+ createdAt: new Date().toISOString(),
};
+ const putCommand = new PutItemCommand({
+ TableName: genericConfig.RsvpDynamoTableName, // Add this config
+ Item: marshall(entry),
+ });
+ await fastify.dynamoClient.send(putCommand);
+ return reply.status(201).send(entry);
},You'll also need to import PutItemCommand from @aws-sdk/client-dynamodb and marshall from @aws-sdk/util-dynamodb.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/api/routes/rsvp.ts around lines 72-79 the POST handler builds an RSVP
entry but never persists it or responds to the client and createdAt is empty;
fix by importing PutItemCommand from @aws-sdk/client-dynamodb and marshall from
@aws-sdk/util-dynamodb, set entry.createdAt = new Date().toISOString(), create a
PutItemCommand with TableName and Item: marshall(entry), await
dynamoDbClient.send(putCmd) (wrap in try/catch to handle errors), and finally
return an appropriate HTTP response (e.g., 201 with the saved entry or a
location header) or a 500 on failure.
| fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().get( | ||
| "/:orgId/event/:eventId", | ||
| { | ||
| schema: withTags(["RSVP"], { | ||
| summary: "Get all RSVPs for an event.", | ||
| params: z.object({ | ||
| eventId: z.string().min(1).meta({ | ||
| description: "The previously-created event ID in the events API.", | ||
| }), | ||
| orgId: z.string().min(1).meta({ | ||
| description: "The organization ID the event belongs to.", | ||
| }), | ||
| }), | ||
| headers: z.object({ | ||
| "x-uiuc-token": z.jwt().min(1).meta({ | ||
| description: | ||
| "An access token for the user in the UIUC Entra ID tenant.", | ||
| }), | ||
| }), | ||
| }), | ||
| }, | ||
| async (request, reply) => { | ||
| const commnand = new QueryCommand({ | ||
| TableName: genericConfig.EventsDynamoTableName, | ||
| IndexName: "EventIdIndex", | ||
| KeyConditionExpression: "eventId = :eid", | ||
| ExpressionAttributeValues: { | ||
| ":eid": { S: request.params.eventId }, | ||
| }, | ||
| }); | ||
| const response = await fastify.dynamoClient.send(commnand); | ||
| const items = response.Items?.map((item) => unmarshall(item)) || []; | ||
| return reply.send(items as z.infer<typeof rsvpListSchema>); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GET handler lacks authorization — any authenticated user can retrieve all RSVPs.
The endpoint validates the UIUC token but doesn't verify that the requester has permission to view RSVPs for the given organization/event. The orgId parameter is defined but unused. Based on relevant snippets, getUserOrgRoles (already imported) and withRoles could be used for authorization.
Consider adding authorization logic to verify the user has appropriate roles for the organization:
const accessToken = request.headers["x-uiuc-token"];
const verifiedData = await verifyUiucAccessToken({
accessToken,
logger: request.log,
});
const netId = verifiedData.userPrincipalName.replace("@illinois.edu", "");
// Check if user has permission to view RSVPs for this org
const userRoles = await getUserOrgRoles(netId, request.params.orgId, fastify);
if (!userRoles.includes("manage:events") && !userRoles.includes("admin")) {
throw new UnauthorizedError({ message: "Not authorized to view RSVPs for this organization." });
}🤖 Prompt for AI Agents
In src/api/routes/rsvp.ts around lines 81 to 114, the GET handler validates the
UIUC token but does not use orgId or enforce authorization, allowing any
authenticated user to retrieve RSVPs; fix by extracting and verifying the
x-uiuc-token to get the user's principal (netId), use getUserOrgRoles(fastify,
netId, request.params.orgId) to fetch roles for the org, and reject requests
that lack required roles (e.g., "manage:events" or "admin") by throwing an
UnauthorizedError before querying DynamoDB; ensure orgId from params is actually
used and log or return a clear unauthorized response when role checks fail.
| const commnand = new QueryCommand({ | ||
| TableName: genericConfig.EventsDynamoTableName, | ||
| IndexName: "EventIdIndex", | ||
| KeyConditionExpression: "eventId = :eid", | ||
| ExpressionAttributeValues: { | ||
| ":eid": { S: request.params.eventId }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong DynamoDB table name — querying events table instead of RSVP table.
The GET handler queries genericConfig.EventsDynamoTableName but should query the new RSVP table (${ProjectId}-events-rsvp from Terraform). Also, there's a typo: commnand → command.
- const commnand = new QueryCommand({
- TableName: genericConfig.EventsDynamoTableName,
+ const command = new QueryCommand({
+ TableName: genericConfig.RsvpDynamoTableName, // Add this to config
IndexName: "EventIdIndex",
KeyConditionExpression: "eventId = :eid",
ExpressionAttributeValues: {
":eid": { S: request.params.eventId },
},
});
- const response = await fastify.dynamoClient.send(commnand);
+ const response = await fastify.dynamoClient.send(command);You'll need to add RsvpDynamoTableName to genericConfig in src/common/config.ts.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/api/routes/rsvp.ts around lines 103 to 110, the code is querying the
Events table and has a typo in the variable name; change the QueryCommand
instantiation to use genericConfig.RsvpDynamoTableName (the Terraform table is
`${ProjectId}-events-rsvp`) instead of genericConfig.EventsDynamoTableName, and
rename the variable from commnand to command. Also add a new RsvpDynamoTableName
entry to src/common/config.ts (populate it from the same environment/terraform
value as `${ProjectId}-events-rsvp`) so genericConfig.RsvpDynamoTableName is
available for use. Ensure any references/imports to genericConfig remain correct
after this change.
| resource "aws_dynamodb_table" "store_limits" { | ||
| region = "us-east-2" | ||
| billing_mode = "PAY_PER_REQUEST" | ||
| name = "${var.ProjectId}-events-rsvp" | ||
| deletion_protection_enabled = true | ||
| hash_key = "partitionKey" | ||
| point_in_time_recovery { | ||
| enabled = true | ||
| } | ||
| attribute { | ||
| name = "partitionKey" | ||
| type = "S" | ||
| } | ||
| attribute { | ||
| name = "eventId" | ||
| type = "S" | ||
| } | ||
| global_secondary_index { | ||
| name = "EventIdIndex" | ||
| hash_key = "eventId" | ||
| projection_type = "ALL" | ||
| } | ||
| stream_enabled = true | ||
| stream_view_type = "NEW_AND_OLD_IMAGES" | ||
| dynamic "replica" { | ||
| for_each = var.ReplicationRegions | ||
| content { | ||
| region_name = replica.value | ||
| deletion_protection_enabled = true | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate Terraform resource name will cause deployment failure.
The resource name store_limits is already used at line 542. Terraform requires unique resource names within the same module. This will cause a duplicate resource error during terraform plan/apply.
Apply this diff to fix the resource name:
-resource "aws_dynamodb_table" "store_limits" {
+resource "aws_dynamodb_table" "events_rsvp" {
region = "us-east-2"
billing_mode = "PAY_PER_REQUEST"
name = "${var.ProjectId}-events-rsvp"Additionally, consider whether this table needs a TTL attribute (like expiresAt) for automatic cleanup of old RSVPs, as most other tables in this module have TTL configured.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| resource "aws_dynamodb_table" "store_limits" { | |
| region = "us-east-2" | |
| billing_mode = "PAY_PER_REQUEST" | |
| name = "${var.ProjectId}-events-rsvp" | |
| deletion_protection_enabled = true | |
| hash_key = "partitionKey" | |
| point_in_time_recovery { | |
| enabled = true | |
| } | |
| attribute { | |
| name = "partitionKey" | |
| type = "S" | |
| } | |
| attribute { | |
| name = "eventId" | |
| type = "S" | |
| } | |
| global_secondary_index { | |
| name = "EventIdIndex" | |
| hash_key = "eventId" | |
| projection_type = "ALL" | |
| } | |
| stream_enabled = true | |
| stream_view_type = "NEW_AND_OLD_IMAGES" | |
| dynamic "replica" { | |
| for_each = var.ReplicationRegions | |
| content { | |
| region_name = replica.value | |
| deletion_protection_enabled = true | |
| } | |
| } | |
| } | |
| resource "aws_dynamodb_table" "events_rsvp" { | |
| region = "us-east-2" | |
| billing_mode = "PAY_PER_REQUEST" | |
| name = "${var.ProjectId}-events-rsvp" | |
| deletion_protection_enabled = true | |
| hash_key = "partitionKey" | |
| point_in_time_recovery { | |
| enabled = true | |
| } | |
| attribute { | |
| name = "partitionKey" | |
| type = "S" | |
| } | |
| attribute { | |
| name = "eventId" | |
| type = "S" | |
| } | |
| global_secondary_index { | |
| name = "EventIdIndex" | |
| hash_key = "eventId" | |
| projection_type = "ALL" | |
| } | |
| stream_enabled = true | |
| stream_view_type = "NEW_AND_OLD_IMAGES" | |
| dynamic "replica" { | |
| for_each = var.ReplicationRegions | |
| content { | |
| region_name = replica.value | |
| deletion_protection_enabled = true | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In terraform/modules/dynamo/main.tf around lines 571 to 602 the
aws_dynamodb_table resource is declared with the name "store_limits", which
duplicates an earlier resource at line 542 and will cause a Terraform duplicate
resource error; change the resource block name to a unique identifier (for
example "store_limits_rsvp" or similar), update all internal references in this
module to use the new resource name, and (optionally) add a ttl block with an
attribute like "expiresAt" (and an attribute definition for that field)
consistent with other tables in this module to enable automatic cleanup of old
RSVPs.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.