fix(cards): prevent concurrent deletions from removing all user cards#376
fix(cards): prevent concurrent deletions from removing all user cards#376Ridanshi wants to merge 1 commit into
Conversation
dea2add to
1f7baae
Compare
|
@Ridanshi Fix merge conflicts and please add terminal screen short proof for unit tests. |
1f7baae to
a16b19a
Compare
|
Conflicts resolved and branch updated. Resolution summary:
Unit tests rerun successfully: npx vitest run src/tests/cards.test.ts --reporter=verbose Result: This includes the concurrency regression coverage added in this PR:
|
|
Hi, could someone please review this PR when convenient? I've completed the implementation and tested it locally. Happy to make any required changes. Thanks! |
Harxhit
left a comment
There was a problem hiding this comment.
Please address lint errors.
The DELETE /api/cards/:id handler performed an ownership lookup, a card count check, and the delete as three separate non-transactional operations. Under concurrent requests both could observe count > 1 and both proceed to delete, leaving the user with zero cards. Move the count guard, optional default-card promotion, and the delete into a single Prisma $transaction with Serializable isolation. The database now serializes concurrent count reads against the write, rolling back the second conflicting transaction so the invariant (user always retains at least one card) cannot be violated even under load. Adds a focused test suite covering normal deletion, last-card rejection, default-card promotion, and four concurrency-guard scenarios.
a16b19a to
5068940
Compare
|
@Ridanshi is attempting to deploy a commit to the Prashantkumar Khatri's projects Team on Vercel. A member of the Team first needs to authorize it. |
CI — All Checks PassedBackend — PASS
Mobile — SKIP
Web — SKIP
Last updated: |
|
I've addressed the lint issues and pushed the fixes. All backend checks are now passing. Could you please re-review the PR? |
|
It looks like this PR still uses the previous PNPM workspace configuration. The repository has been migrated to NPM workspaces, so the dependency configuration should be updated accordingly before this can be merged. |

Summary
Fixes a transactional race condition in card deletion that could allow concurrent requests to remove all cards belonging to a user despite intended last-card protection.
Root Cause
The delete flow previously executed:
as separate non-transactional database operations.
Under concurrent requests, multiple deletes could simultaneously observe the same card count and proceed, resulting in users ending up with zero cards.
Fix
Moved the deletion guard and delete flow into a single Prisma
$transactionusingSerializableisolation.The transaction now atomically performs:
This guarantees that concurrent delete requests cannot violate the invariant that a user must always retain at least one card.
Concurrency Behavior
Before:
After:
Tests Added
Added focused regression coverage for:
Files Changed
apps/backend/src/routes/cards.tsapps/backend/src/__tests__/cards.test.tsCloses #356