feat(events): add mutations for managing event RSVPs with optimistic concurrency (#1363) - #1797
Conversation
…concurrency (krushit1307#1363) - Add version and available_spots tracking columns to events table. - Create public.manage_event_rsvp RPC function using pessimistic row-level locking (SELECT ... FOR UPDATE) and version increment logic to prevent race conditions and overbooking. - Ensure event capacity (max_attendees) is strictly enforced and available_spots counter never drops below 0. - Expose rsvpToEvent GraphQL mutation and RsvpPayload type in GraphQL Yoga resolvers/schema. - Add pgTAP test suite (supabase/tests/rsvp_concurrency.test.sql) verifying capacity limits, duplicate RSVP prevention, spot release on cancellation, and locking. - Add GraphQL integration tests in src/graphql/server.test.ts (13/13 passing). Closes krushit1307#1363
📝 WalkthroughWalkthroughChangesEvent RSVP concurrency
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant GraphQL
participant SupabaseRPC
participant PostgreSQL
Client->>GraphQL: rsvpToEvent(eventId, userId, action)
GraphQL->>SupabaseRPC: manage_event_rsvp parameters
SupabaseRPC->>PostgreSQL: lock event and update RSVP state
PostgreSQL-->>SupabaseRPC: status, available_spots, version
SupabaseRPC-->>GraphQL: JSONB response
GraphQL-->>Client: RsvpPayload
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql (1)
139-147: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winCANCEL hard-deletes and always reports success, contradicting the
CANCELLEDstatus model used elsewhere.Every other branch treats
status = 'CANCELLED'as the cancellation representation, but here the row is removed, so cancellations leave no trace and a cancel for a user who never RSVPed still returnsCANCEL_SUCCESS. Consider anUPDATE ... SET status = 'CANCELLED'and returningNOT_RSVPEDwhen no row was affected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql` around lines 139 - 147, Update the CANCEL/REMOVE/UNRSVP branch to mark the matching event_rsvps row with status 'CANCELLED' instead of deleting it. Track the affected-row count and return NOT_RSVPED when no row exists for the effective user; only report cancellation success after an update, while preserving the existing RSVP recount behavior.supabase/schema.sql (1)
102-103: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd a
CHECK (available_spots >= 0)constraint.Issue
#1363requiresavailable_spotsnever becomes negative; today that invariant only lives in the RPC'sGREATEST(0, ...). A column constraint enforces it for any other writer.🛡️ Proposed constraint
- available_spots INTEGER, + available_spots INTEGER CHECK (available_spots IS NULL OR available_spots >= 0), version INTEGER NOT NULL DEFAULT 1,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@supabase/schema.sql` around lines 102 - 103, Add a database CHECK constraint to the available_spots column definition requiring values to be greater than or equal to zero, while preserving its nullable behavior and the existing version definition. Ensure this constraint applies independently of the RPC logic to protect all writers.graphql/resolvers/index.ts (1)
213-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate snake_case + camelCase fields double the
EventAPI surface.
max_attendees/maxAttendeesandavailable_spots/availableSpotsare aliases for the same data. If camelCase is the direction, mark the snake_case variants@deprecatedrather than maintaining both indefinitely.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@graphql/resolvers/index.ts` around lines 213 - 217, Deprecate the snake_case Event fields max_attendees and available_spots in the GraphQL schema while retaining maxAttendees and availableSpots as the preferred fields. Add appropriate `@deprecated` metadata to the snake_case variants without changing their existing data behavior.src/graphql/server.test.ts (1)
78-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMock never exercises the resolver's error/fallback paths.
erroris alwaysnullanddatais nevernull, so thethrow new Error(error.message)branch and the?? "ERROR"/?? nulldefaults inrsvpToEventare untested. Adding anevt-errorand anevt-null-datacase would cover both cheaply.p_actionis also ignored, so a CANCEL case can't be distinguished.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/graphql/server.test.ts` around lines 78 - 109, Update the rpc mock in the GraphQL test to add evt-error and evt-null-data responses, exercising the resolver’s error throw and fallback defaults in rsvpToEvent. Use a non-null error with a message for evt-error and null data for evt-null-data, while preserving existing RSVP behavior. Also branch on args.p_action so CANCEL requests receive distinguishable mock behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql`:
- Around line 23-31: Bind RSVP mutations to the authenticated actor at both
layers: in supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql,
update manage_event_rsvp to reject mismatched p_user_id unless the caller is
service_role; in graphql/resolvers/index.ts lines 438-458, make rsvpToEvent
derive the user ID from request context, and remove or admin-gate the
client-supplied userId schema argument.
- Around line 11-19: The migration references missing event_rsvps.status and
events.requires_approval columns. At
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql lines 11-19,
add event_rsvps.status with the specified default before the backfill and mirror
it in supabase/schema.sql; at lines 58-62, add events.requires_approval with its
specified default in both locations, or remove the corresponding references
while preserving confirmed RSVP behavior.
- Around line 176-177: Update the grants for public.manage_event_rsvp(UUID,
UUID, TEXT) to explicitly revoke EXECUTE from PUBLIC before granting it to
authenticated and service_role, ensuring anonymous callers cannot invoke this
SECURITY DEFINER function.
In `@supabase/tests/rsvp_concurrency.test.sql`:
- Line 17: Remove the blanket GRANT ALL statement from the RSVP concurrency test
setup. Keep the test focused on its assertions and do not replace it with
broader schema-wide privileges.
- Around line 52-99: Replace the sequential RSVP assertions in the concurrency
test with a multi-session concurrency harness using pg_isolation_tester, dblink,
or parallel RPC calls, so multiple users attempt RSVPs simultaneously. Validate
that exactly max_attendees (including the 50-capacity/51-attempt case) succeed,
the remaining attempt returns EVENT_FULL, and the final event_rsvps count never
exceeds capacity; retain cancellation and freed-spot coverage where applicable.
- Line 61: Correct the malformed event UUID argument in the manage_event_rsvp
call within the concurrency test, reducing the final group to a valid
12-hex-digit UUID while preserving the intended event identifier and all other
test inputs.
---
Nitpick comments:
In `@graphql/resolvers/index.ts`:
- Around line 213-217: Deprecate the snake_case Event fields max_attendees and
available_spots in the GraphQL schema while retaining maxAttendees and
availableSpots as the preferred fields. Add appropriate `@deprecated` metadata to
the snake_case variants without changing their existing data behavior.
In `@src/graphql/server.test.ts`:
- Around line 78-109: Update the rpc mock in the GraphQL test to add evt-error
and evt-null-data responses, exercising the resolver’s error throw and fallback
defaults in rsvpToEvent. Use a non-null error with a message for evt-error and
null data for evt-null-data, while preserving existing RSVP behavior. Also
branch on args.p_action so CANCEL requests receive distinguishable mock
behavior.
In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql`:
- Around line 139-147: Update the CANCEL/REMOVE/UNRSVP branch to mark the
matching event_rsvps row with status 'CANCELLED' instead of deleting it. Track
the affected-row count and return NOT_RSVPED when no row exists for the
effective user; only report cancellation success after an update, while
preserving the existing RSVP recount behavior.
In `@supabase/schema.sql`:
- Around line 102-103: Add a database CHECK constraint to the available_spots
column definition requiring values to be greater than or equal to zero, while
preserving its nullable behavior and the existing version definition. Ensure
this constraint applies independently of the RPC logic to protect all writers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 23cda6da-a426-4cb4-a1ef-3d9be46c8e65
📒 Files selected for processing (5)
graphql/resolvers/index.tssrc/graphql/server.test.tssupabase/migrations/20260729120000_concurrency_rsvp_mutation.sqlsupabase/schema.sqlsupabase/tests/rsvp_concurrency.test.sql
| UPDATE public.events e | ||
| SET available_spots = CASE | ||
| WHEN e.max_attendees IS NULL THEN NULL | ||
| ELSE GREATEST(0, e.max_attendees - ( | ||
| SELECT COUNT(*) FROM public.event_rsvps r | ||
| WHERE r.event_id = e.id AND (r.status IS NULL OR r.status NOT IN ('CANCELLED', 'REJECTED')) | ||
| )) | ||
| END | ||
| WHERE e.available_spots IS NULL AND e.max_attendees IS NOT NULL; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
The new RPC references two columns that don't exist in supabase/schema.sql. event_rsvps.status and events.requires_approval are absent from the table definitions (lines 141-148 and 88-109), so the migration's backfill fails immediately and the function body fails on every call. Root cause: neither column is created by this PR.
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L11-L19: addALTER TABLE public.event_rsvps ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'CONFIRMED';before the backfill, and mirror it insupabase/schema.sql, or remove thestatusfilters entirely.supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L58-L62: addrequires_approval BOOLEAN NOT NULL DEFAULT FALSEtoeventsin both this migration andsupabase/schema.sql, or droprequires_approvalfrom theSELECT ... INTOand hardcode'CONFIRMED'.
📍 Affects 1 file
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L11-L19(this comment)supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L58-L62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql` around
lines 11 - 19, The migration references missing event_rsvps.status and
events.requires_approval columns. At
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql lines 11-19,
add event_rsvps.status with the specified default before the backfill and mirror
it in supabase/schema.sql; at lines 58-62, add events.requires_approval with its
specified default in both locations, or remove the corresponding references
while preserving confirmed RSVP behavior.
| CREATE OR REPLACE FUNCTION public.manage_event_rsvp( | ||
| p_event_id UUID, | ||
| p_user_id UUID DEFAULT auth.uid(), | ||
| p_action TEXT DEFAULT 'RSVP' | ||
| ) | ||
| RETURNS JSONB | ||
| LANGUAGE plpgsql | ||
| SECURITY DEFINER | ||
| SET search_path = public |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Any caller can RSVP or cancel on behalf of an arbitrary user. The actor is never bound to the authenticated session at either layer: the RPC runs SECURITY DEFINER (bypassing RLS) and trusts p_user_id, and the resolver forwards a client-supplied userId verbatim.
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L23-L31: reject the call whenp_user_iddiffers fromauth.uid()unless the caller isservice_role.graphql/resolvers/index.ts#L438-L458: derive the user id from the GraphQL request context instead of theuserIdargument, and remove or admin-gate that argument in thersvpToEventschema field.
📍 Affects 2 files
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql#L23-L31(this comment)graphql/resolvers/index.ts#L438-L458
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql` around
lines 23 - 31, Bind RSVP mutations to the authenticated actor at both layers: in
supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql, update
manage_event_rsvp to reject mismatched p_user_id unless the caller is
service_role; in graphql/resolvers/index.ts lines 438-458, make rsvpToEvent
derive the user ID from request context, and remove or admin-gate the
client-supplied userId schema argument.
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO authenticated; | ||
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO service_role; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Revoke default PUBLIC EXECUTE on this SECURITY DEFINER function.
Postgres grants EXECUTE to PUBLIC by default, so anon can invoke it regardless of these explicit grants.
🔒 Tighten grants
+REVOKE ALL ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO authenticated;
GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO service_role;📝 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.
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO authenticated; | |
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO service_role; | |
| REVOKE ALL ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) FROM PUBLIC; | |
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO authenticated; | |
| GRANT EXECUTE ON FUNCTION public.manage_event_rsvp(UUID, UUID, TEXT) TO service_role; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/migrations/20260729120000_concurrency_rsvp_mutation.sql` around
lines 176 - 177, Update the grants for public.manage_event_rsvp(UUID, UUID,
TEXT) to explicitly revoke EXECUTE from PUBLIC before granting it to
authenticated and service_role, ensuring anonymous callers cannot invoke this
SECURITY DEFINER function.
| SELECT plan(8); | ||
|
|
||
| -- Grant schema privileges | ||
| GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated, anon; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Drop this blanket GRANT ALL.
Granting all privileges on every public table to authenticated and anon is unrelated to the assertions and, if this file is ever run outside a rolled-back transaction, materially weakens the schema's posture.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/tests/rsvp_concurrency.test.sql` at line 17, Remove the blanket
GRANT ALL statement from the RSVP concurrency test setup. Keep the test focused
on its assertions and do not replace it with broader schema-wide privileges.
| -- Test 2: User 1 RSVPs successfully | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'), | ||
| 'RSVP_SUCCESS', | ||
| 'User 1 should successfully RSVP for available spot 1 of 2' | ||
| ); | ||
|
|
||
| -- Test 3: User 1 tries duplicate RSVP -> ALREADY_RSVPED | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-0000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'), | ||
| 'ALREADY_RSVPED', | ||
| 'Duplicate RSVP from User 1 should return ALREADY_RSVPED' | ||
| ); | ||
|
|
||
| -- Test 4: User 2 RSVPs successfully (2nd spot filled, max_attendees = 2) | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000002', 'RSVP')->>'code'), | ||
| 'RSVP_SUCCESS', | ||
| 'User 2 should successfully RSVP for spot 2 of 2' | ||
| ); | ||
|
|
||
| -- Test 5: User 3 tries to RSVP when event is at capacity (2/2 filled) -> EVENT_FULL | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000003', 'RSVP')->>'code'), | ||
| 'EVENT_FULL', | ||
| 'User 3 RSVP attempt when 2/2 spots filled should return EVENT_FULL' | ||
| ); | ||
|
|
||
| -- Test 6: Verify total count of RSVPs in table remains strictly 2 (51st/3rd RSVP blocked) | ||
| SELECT results_eq( | ||
| 'SELECT COUNT(*)::integer FROM public.event_rsvps WHERE event_id = ''e1000000-0000-0000-0000-000000000200''', | ||
| ARRAY[2], | ||
| 'Total RSVP count in table must equal max_attendees capacity (2)' | ||
| ); | ||
|
|
||
| -- Test 7: User 1 cancels RSVP -> spot is freed | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000001', 'CANCEL')->>'code'), | ||
| 'CANCEL_SUCCESS', | ||
| 'User 1 RSVP cancellation should succeed and free a spot' | ||
| ); | ||
|
|
||
| -- Test 8: User 3 can now RSVP into the newly freed spot | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000003', 'RSVP')->>'code'), | ||
| 'RSVP_SUCCESS', | ||
| 'User 3 should successfully RSVP into the freed spot' | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
These tests are sequential, not concurrent — the acceptance criteria are not covered.
All eight assertions run in a single session/transaction, so SELECT ... FOR UPDATE never contends and the "50 spots cannot receive 51 RSVPs" criterion from #1363 is untested. pgTAP alone cannot do this; a multi-session harness (e.g. pg_isolation_tester/dblink, or a Node script firing N parallel RPC calls) is needed. Want me to open an issue for the concurrency harness?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/tests/rsvp_concurrency.test.sql` around lines 52 - 99, Replace the
sequential RSVP assertions in the concurrency test with a multi-session
concurrency harness using pg_isolation_tester, dblink, or parallel RPC calls, so
multiple users attempt RSVPs simultaneously. Validate that exactly max_attendees
(including the 50-capacity/51-attempt case) succeed, the remaining attempt
returns EVENT_FULL, and the final event_rsvps count never exceeds capacity;
retain cancellation and freed-spot coverage where applicable.
|
|
||
| -- Test 3: User 1 tries duplicate RSVP -> ALREADY_RSVPED | ||
| SELECT is( | ||
| (public.manage_event_rsvp('e1000000-0000-0000-0000-0000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Malformed UUID — the whole test file aborts here.
'e1000000-0000-0000-0000-0000000000200' has 13 hex digits in the last group; Postgres raises invalid input syntax for type uuid.
🐛 Fix the event id
- (public.manage_event_rsvp('e1000000-0000-0000-0000-0000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'),
+ (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'),📝 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.
| (public.manage_event_rsvp('e1000000-0000-0000-0000-0000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'), | |
| (public.manage_event_rsvp('e1000000-0000-0000-0000-000000000200', 'e1000000-0000-0000-0000-000000000001', 'RSVP')->>'code'), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/tests/rsvp_concurrency.test.sql` at line 61, Correct the malformed
event UUID argument in the manage_event_rsvp call within the concurrency test,
reducing the final group to a valid 12-hex-digit UUID while preserving the
intended event identifier and all other test inputs.
Pull Request
Description
Implements concurrent-safe RSVP management with pessimistic row-level locking (
SELECT ... FOR UPDATE) and optimistic concurrency version tracking for CampusConnect (#1363).When high-demand campus events open for registration, simultaneous user submissions are processed safely without overbooking or race conditions. The event's
max_attendeescapacity is strictly enforced, ensuring theavailable_spotscounter never drops below zero.version(INTEGER DEFAULT 1) andavailable_spotscolumns to theeventstable.public.manage_event_rsvp(p_event_id, p_user_id, p_action)RPC function with transaction-levelSELECT ... FOR UPDATErow locking.ALREADY_RSVPED) and enforced capacity limits (EVENT_FULL).rsvpToEvent(eventId: ID!, userId: ID, action: String): RsvpPayload!mutation andRsvpPayloadtype definition ingraphql/resolvers/index.ts.maxAttendees,availableSpots, andversionfields on the GraphQLEventtype.supabase/tests/rsvp_concurrency.test.sqlto verify capacity enforcement, locking behavior, duplicate handling, and spot release on cancellation.src/graphql/server.test.ts(13/13 passing).Type of Change
Related Issue
Closes #1363
Testing
Describe the testing performed.
Tested locally
Existing functionality verified
No new warnings or errors
pgTAP Test Suite (
supabase/tests/rsvp_concurrency.test.sql):public.manage_event_rsvpsignature & execution.EVENT_FULL,available_spotsremains 0).max_attendees.CANCEL_SUCCESS, spot count restored, 3rd user can then successfully register).GraphQL Integration Tests (
src/graphql/server.test.ts):rsvpToEventmutation execution andRsvpPayloadschema fields (success,code,message,availableSpots,status,version).npx tsc --noEmitpassed with 0 type errors.npx eslint --max-warnings=0passed with 0 warnings/errors.Screenshots
N/A
Checklist
Summary by CodeRabbit
New Features
Bug Fixes
Tests