Skip to content

refactor: implement optimistic locking using version columns (#2408) - #2465

Merged
krushit1307 merged 2 commits into
krushit1307:mainfrom
Shruti070107:feature/optimistic-database-locking
Aug 5, 2026
Merged

refactor: implement optimistic locking using version columns (#2408)#2465
krushit1307 merged 2 commits into
krushit1307:mainfrom
Shruti070107:feature/optimistic-database-locking

Conversation

@Shruti070107

@Shruti070107 Shruti070107 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

Implements optimistic database locking using version columns on the events table. Simultaneous edits to a single event no longer blindly overwrite each other: the UPDATE payload now requires the version the user fetched, the query uses WHERE id = $1 AND version = $2, and when 0 rows are affected the save is rejected with a conflict. The merge-conflict modal then shows the fresh database state so the user never loses their work (UI recovery). The same OCC guard is applied to event reschedules via adminEventRescheduleApi, and the Edit Event dialog is now wired into the event detail page for organizers.

Type of Change

  • New Feature
  • Bug Fix
  • Documentation Update
  • Refactor
  • Performance Improvement
  • Security Improvement
  • Other

Related Issue

Closes #2408

Testing

Describe the testing performed.

  • Ran tsc --noEmit — 0 errors in all changed files

  • src/components/EditEventDialog.test.tsx (2 tests): verifies a stale version (0 rows updated) triggers the merge-conflict modal with the new server state, and that a matching version saves successfully

  • supabase/tests/event_version_concurrency.test.sql: pgTAP test asserting a stale-version UPDATE affects 0 rows

  • Existing conflictResolution tests pass

  • Tested locally

  • Existing functionality verified

  • No new warnings or errors

Screenshots

N/A

Checklist

  • Code follows project conventions
  • Documentation updated where required
  • No unnecessary files included
  • Changes have been tested
  • Ready for review

Summary by CodeRabbit

  • New Features

    • Added organizer event editing directly from event details.
    • Added conflict detection and resolution when events are modified simultaneously.
    • Rescheduling now protects against overwriting newer changes.
  • Bug Fixes

    • Prevented stale edits from replacing the latest event information.
    • Improved recovery by showing current server changes alongside local edits.
  • Tests

    • Added coverage for safe concurrent editing, rescheduling, conflict recovery, and successful saves.

…rushit1307#2408)

Enforce optimistic concurrency control on event writes so simultaneous
edits cannot silently overwrite each other (lost update anomaly).

- EditEventDialog: UPDATE is now guarded with WHERE id AND version, and
  increments version atomically. When 0 rows are affected, the save is
  rejected and a merge-conflict modal shows the fresh database state.
- Wire the EditEventDialog into the event detail page for organizers and
  select version/version_vector/category_id/tags in the event query.
- adminEventRescheduleApi: reschedules bump the version and use the same
  OCC predicate, throwing a conflict error on a stale version.
- Add pgTAP concurrency test and a component test covering the conflict path.
@github-actions github-actions Bot added backend bug Something isn't working database design ECSoC26 Elite Coders Summer Of Code'26 - Open Source Program frontend refactor security labels Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Event editing and rescheduling now use optimistic concurrency control. Saves require the observed event version. Stale edits trigger three-way conflict recovery. The event route renders organizer editing controls, and database tests verify guarded version updates.

Changes

Event concurrency control

Layer / File(s) Summary
Edit conflict flow
src/components/EditEventDialog.tsx, src/components/EditEventDialog.test.tsx
Event saves require the observed version. Stale saves fetch server data, merge local edits, and open conflict resolution. Tests cover stale and successful saves.
Organizer edit integration
src/routes/events.$eventId.tsx
The event route loads version metadata and renders EditEventDialog for organizers.
Reschedule API locking
src/routes/api/events/$id/reschedule.ts, src/services/adminEventRescheduleApi.ts
The rescheduling API validates versions and applies guarded updates that increment the event version.
Database concurrency validation
supabase/tests/event_version_concurrency.test.sql
pgTAP tests verify version columns, successful guarded updates, and stale-update rejection without data changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: optimization, testing

Sequence Diagram(s)

sequenceDiagram
  participant Organizer
  participant EditEventDialog
  participant Supabase
  participant ConflictResolutionModal
  Organizer->>EditEventDialog: submit event edits
  EditEventDialog->>Supabase: update where id and version match
  Supabase-->>EditEventDialog: updated row or zero rows
  EditEventDialog->>Supabase: fetch latest event after conflict
  Supabase-->>EditEventDialog: latest server event
  EditEventDialog->>ConflictResolutionModal: display merged server and local edits
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: implementing optimistic locking with version columns.
Linked Issues check ✅ Passed The changes implement version-guarded event updates, conflict detection, latest-state recovery, rescheduling protection, and concurrency tests for issue #2408.
Out of Scope Changes check ✅ Passed The changes remain within scope by applying optimistic locking to event editing and rescheduling, wiring the edit dialog, and adding related tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@src/components/EditEventDialog.test.tsx`:
- Around line 128-137: In the successful save test, add an assertion on
mockUpdate before the dialog-closed check to verify it was called with the
expected update payload, including version: 2. Keep the existing mocked response
and close assertion unchanged.

In `@src/services/adminEventRescheduleApi.ts`:
- Around line 30-42: Update the primary REST request in the rescheduling flow to
include targetVersion in its request body, then update the corresponding REST
handler to require that value for its guarded update before returning success.
Preserve the existing direct Supabase fallback behavior and ensure the handler
uses the supplied version for optimistic concurrency control.

In `@supabase/tests/event_version_concurrency.test.sql`:
- Around line 41-50: Update the concurrency test around the guarded UPDATE for
event ID 90000000-0000-0000-0000-000000000004 to first change version 1 to 2 and
assert that it succeeds, then issue a second guarded update expecting version 1
and assert it affects zero rows. Verify the stored description remains the first
writer’s value.
🪄 Autofix

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: a21bf977-682a-4eab-aa76-78900967c897

📥 Commits

Reviewing files that changed from the base of the PR and between 206bdee and d41dce7.

📒 Files selected for processing (5)
  • src/components/EditEventDialog.test.tsx
  • src/components/EditEventDialog.tsx
  • src/routes/events.$eventId.tsx
  • src/services/adminEventRescheduleApi.ts
  • supabase/tests/event_version_concurrency.test.sql

Comment thread src/components/EditEventDialog.test.tsx
Comment on lines +30 to +42
// Read the current version so the write can be guarded with OCC
const { data: current, error: fetchError } = await supabase
.from("events")
.select("version")
.eq("id", eventId)
.maybeSingle();

if (fetchError) {
throw new Error(`Failed to load event version: ${fetchError.message}`);
}

const targetVersion = current?.version ?? 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Send the fetched version to the REST endpoint.

targetVersion is only used by the direct Supabase fallback. The primary REST request does not send an expected version. The REST handler cannot apply the client-fetched version on this path.

Send targetVersion in the request body and require the endpoint to use it in its guarded update before it returns success.

🤖 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/services/adminEventRescheduleApi.ts` around lines 30 - 42, Update the
primary REST request in the rescheduling flow to include targetVersion in its
request body, then update the corresponding REST handler to require that value
for its guarded update before returning success. Preserve the existing direct
Supabase fallback behavior and ensure the handler uses the supplied version for
optimistic concurrency control.

Comment thread supabase/tests/event_version_concurrency.test.sql Outdated
…2408)

- EditEventDialog.test.tsx: assert the successful save writes version 2
  in the update payload before the dialog closes.
- adminEventRescheduleApi: send the fetched targetVersion in the REST
  reschedule request body so the endpoint can enforce OCC.
- Add the reschedule REST handler that requires the supplied version for
  its guarded UPDATE and returns 409 on a stale version.
- event_version_concurrency.test.sql: simulate two writers - a guarded
  update from version 1 to 2 succeeds, then a stale version 1 update
  affects 0 rows and preserves the first writer's data.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/routes/api/events/`$id/reschedule.ts:
- Around line 46-50: The guarded-update conflict branch in the reschedule route
should return a structured 409 payload containing the current event fields and
version, rather than text alone. Update the client flow in the reschedule API
service to detect 409 responses, consume that payload for conflict recovery, and
avoid falling through to the direct-update fallback.
🪄 Autofix

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: 586435ef-aa88-4911-9fc7-67a9b756f944

📥 Commits

Reviewing files that changed from the base of the PR and between d41dce7 and 13401db.

📒 Files selected for processing (4)
  • src/components/EditEventDialog.test.tsx
  • src/routes/api/events/$id/reschedule.ts
  • src/services/adminEventRescheduleApi.ts
  • supabase/tests/event_version_concurrency.test.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/EditEventDialog.test.tsx
  • src/services/adminEventRescheduleApi.ts

Comment on lines +46 to +50
if (!data || data.length === 0) {
return new Response(
"Conflict: This event was modified by another user. Please refresh and try again.",
{ status: 409 },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Return the current event state with the conflict response.

The 409 response contains only text. It does not provide the current database state required for conflict recovery. src/services/adminEventRescheduleApi.ts:23-108 also does not handle 409 before it attempts the fallback update.

After the guarded update affects zero rows, return a structured conflict payload with the current event fields and version. Update the client to handle 409 and use that payload instead of falling through to the direct-update fallback.

🤖 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/routes/api/events/`$id/reschedule.ts around lines 46 - 50, The
guarded-update conflict branch in the reschedule route should return a
structured 409 payload containing the current event fields and version, rather
than text alone. Update the client flow in the reschedule API service to detect
409 responses, consume that payload for conflict recovery, and avoid falling
through to the direct-update fallback.

@krushit1307 krushit1307 added the Blue All CI checks are passing on this PR label Aug 5, 2026
@krushit1307
krushit1307 merged commit 0b30292 into krushit1307:main Aug 5, 2026
3 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Blue All CI checks are passing on this PR bug Something isn't working database design ECSoC26-L3 ECSoC26 Elite Coders Summer Of Code'26 - Open Source Program frontend refactor security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR]: Implement Optimistic Database Locking using 'version' columns

2 participants