Skip to content

Conversation

@MeghP89
Copy link

@MeghP89 MeghP89 commented Nov 26, 2025

Summary by CodeRabbit

  • New Features
    • Introduced event RSVP functionality with authentication and membership verification
    • New endpoints enable users to submit and retrieve RSVPs for events
    • Rate limiting applied to RSVP submissions
    • Currently available in development environment only

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

This 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

Cohort / File(s) Summary
API Route Registration
src/api/index.ts
Imports and registers rsvpRoutes conditionally under /rsvp path when run environment is dev; follows existing dev-gated route pattern
RSVP Routes Module
src/api/routes/rsvp.ts
New Fastify plugin implementing RSVP endpoints: POST validates UIUC access token, checks paid membership, and constructs RSVP entry (but does not persist or respond); GET queries DynamoDB for RSVPs by eventId via EventIdIndex; includes rate limiting, validation schemas, and authentication middleware
DynamoDB Infrastructure
terraform/modules/dynamo/main.tf
Adds store\_limits DynamoDB table with PAY_PER_REQUEST billing, EventIdIndex Global Secondary Index, stream enabled with NEW_AND_OLD_IMAGES, dynamic multi-region replication across ReplicationRegions, and deletion protection

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • POST endpoint incomplete: The POST handler constructs an RSVP entry but does not persist it to DynamoDB or return a response; verify if this is intentional or a blocker
  • DynamoDB query validation: Confirm the EventIdIndex configuration and query parameters are correct for the expected access patterns
  • Rate limiting configuration: Ensure the rate limiter settings are appropriate for RSVP endpoint usage

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main addition: a GET route to retrieve all RSVPs for an event, which is the primary feature in the rsvp.ts implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch events-rsvp

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.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

💰 Infracost report

Monthly estimate generated

This comment will be updated when code changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: withRoles and getUserOrgRoles.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae6f87 and 9e2f331.

📒 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 rsvpRoutes follows the existing patterns in the codebase. The dev-only gate is consistent with how vendingPlugin is registered.

Also applies to: 381-383

Comment on lines +37 to +41
params: z.object({
eventId: z.string().min(1).meta({
description: "The previously-created event ID in the events API.",
}),
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +43 to +46
"x-uiuc-token": z.jwt().min(1).meta({
description:
"An access token for the user in the UIUC Entra ID tenant.",
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 3

Repository: 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 -60

Repository: 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 2

Repository: 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 ts

Repository: 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.ts

Repository: 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 -20

Repository: 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 -50

Repository: 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 -20

Repository: 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 -20

Repository: 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 -50

Repository: 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 ts

Repository: 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 -30

Repository: 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 -20

Repository: 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 -20

Repository: 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 -50

Repository: 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 -50

Repository: 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.ts

Repository: 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.ts

Repository: 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 ts

Repository: 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 -20

Repository: 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).

Comment on lines +72 to +79
const entry = {
partitionKey: `${request.params.eventId}#${upn}`,
eventId: request.params.eventId,
userId: upn,
isPaidMember,
createdAt: "",
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

POST handler is incomplete — entry is created but never persisted or returned.

The handler constructs an RSVP entry object but:

  1. Never writes it to DynamoDB
  2. Never sends a response to the client
  3. Sets createdAt to 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.

Comment on lines +81 to +114
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>);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +103 to +110
const commnand = new QueryCommand({
TableName: genericConfig.EventsDynamoTableName,
IndexName: "EventIdIndex",
KeyConditionExpression: "eventId = :eid",
ExpressionAttributeValues: {
":eid": { S: request.params.eventId },
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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: commnandcommand.

-      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.

Comment on lines +571 to +602
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
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested 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
}
}
}
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants