Skip to content

fix: wrap doubt like toggle in transaction with floor guard (#472)#497

Closed
vedant7007 wants to merge 1 commit into
knoxiboy:mainfrom
vedant7007:fix/doubt-like-race-condition-472
Closed

fix: wrap doubt like toggle in transaction with floor guard (#472)#497
vedant7007 wants to merge 1 commit into
knoxiboy:mainfrom
vedant7007:fix/doubt-like-race-condition-472

Conversation

@vedant7007

@vedant7007 vedant7007 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Closes #472

the doubt like toggle used a non-atomic check-then-act pattern while the reply vote endpoint was already fixed for the same bug in PR #335. concurrent likes could cause duplicate entries and negative counts.

what i fixed:

  • wrapped entire like toggle in db.transaction() matching the reply vote pattern
  • added GREATEST(likes - 1, 0) floor guard preventing negative counts
  • all operations atomic now

response shape unchanged. typecheck clean, build passes.

for labels i think gssoc:approved + level:intermediate + type:bug + quality:clean fits here. happy to make changes if needed!

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability and data consistency of the like feature with enhanced concurrency safety
    • Added better error handling for invalid or missing doubt entries
    • Strengthened the like toggle mechanism to prevent edge cases

@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

@vedant7007 is attempting to deploy a commit to the Karan Mani Tripathi 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The doubt like action handler is refactored from a non-atomic, race-condition-prone implementation to an atomic transactional flow. The entire toggle is now wrapped in a single database transaction that locks the doubt row, atomically checks for existing likes, performs conditional insert/delete, updates the count with a floor guard, and returns a 404 on missing rows.

Changes

Atomic Like Toggle with Transaction Lock

Layer / File(s) Summary
Transactional like toggle with row lock and atomic count updates
src/app/api/doubts/action/[id]/route.ts
The PATCH handler wraps the entire like toggle in db.transaction() with SELECT ... FOR UPDATE row locking. It atomically checks if the current user already liked the doubt; if so, deletes the like and decrements count using GREATEST(likes - 1, 0), returning hasLiked: false. If not liked, it inserts the like and increments count, returning hasLiked: true. Returns 404 if the doubt row cannot be locked or found.

Sequence Diagram

sequenceDiagram
  participant Handler
  participant Transaction
  participant DoubtsTable
  participant LikesTable
  
  Handler->>Transaction: start transaction
  Transaction->>DoubtsTable: lock doubt row (SELECT FOR UPDATE)
  DoubtsTable-->>Transaction: doubt row
  
  alt doubt not found
    Transaction-->>Handler: return 404
  else doubt found
    Transaction->>LikesTable: check if user already liked
    LikesTable-->>Transaction: existingLike
    
    alt already liked
      Transaction->>LikesTable: delete like
      Transaction->>DoubtsTable: decrement count with GREATEST floor
      Transaction-->>Handler: return {hasLiked: false}
    else not liked
      Transaction->>LikesTable: insert like
      Transaction->>DoubtsTable: increment count
      Transaction-->>Handler: return {hasLiked: true}
    end
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • knoxiboy/DoubtDesk#478: Updates the same doubt like action to use transactional wrapping with row-level locking and guarded count decrements, fixing the same race condition race condition issue.

Suggested labels

gssoc'26, level:intermediate, quality:clean, type:bug, gssoc:approved, mentor:knoxiboy

Suggested reviewers

  • knoxiboy
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: wrapping the doubt like toggle in a transaction with a floor guard, which directly addresses the race condition fix.
Linked Issues check ✅ Passed The changes implement all key requirements from issue #472: transaction wrapping, FOR UPDATE lock, GREATEST floor guard, and non-transactional sequence replacement.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the doubt like toggle race condition as specified in issue #472; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


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
github-actions Bot requested a review from knoxiboy June 1, 2026 14:13
@github-actions github-actions Bot added gssoc'26 GSSoC program issue level:intermediate Intermediate level task type:bug Bug fix labels Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

@coderabbitai review

1 similar comment
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/app/api/doubts/action/`[id]/route.ts:
- Around line 55-56: The like/unlike flow is inconsistent because
action/[id]/route.ts uses secureUserIdentifier = email while the client and
src/app/api/doubts/route.ts use the anonymous_user value as userName, causing
mismatched lookups; fix this by normalizing the identifier server-side: in
action/[id]/route.ts set secureUserIdentifier to the incoming userName (from the
request body/query) when it exists and fall back to email (Clerk) only if
userName is absent, and ensure the same normalization is applied in the doubts
route that computes hasLiked (matching likesTable.userName) so reads, inserts
and deletes use the same key (references: secureUserIdentifier,
likesTable.userName, hasLiked logic in src/app/api/doubts/route.ts).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a877b3a-92c4-4cb8-8e21-e2b948482a55

📥 Commits

Reviewing files that changed from the base of the PR and between 99d5b8b and b5cc8fc.

📒 Files selected for processing (1)
  • src/app/api/doubts/action/[id]/route.ts

Comment on lines +55 to 56

const secureUserIdentifier = email;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the format of existing userName values in likes table
rg -n "likesTable" --type ts -C 5 | head -100

# Check if there's any migration or data cleanup related to likes
fd -i migration -e ts -e sql --exec grep -l "likes" {} \;

Repository: knoxiboy/DoubtDesk

Length of output: 7014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Inspect secureUserIdentifier assignment in action route =="
sed -n '1,140p' src/app/api/doubts/action/[id]/route.ts | nl -ba | sed -n '40,120p'

echo
echo "== Locate anonymous_user usage on the client =="
rg -n 'anonymous_user' --hidden --glob '!.next/*' --glob '!.turbo/*' --glob '!.vercel/*' --glob '!*node_modules*'

echo
echo "== Find where like/unlike request sends userName =="
rg -n "userName" --hidden --glob '!.next/*' --glob '!.turbo/*' --glob '!.vercel/*' --glob '!*node_modules*' src

echo
echo "== Inspect likes table schema definition =="
sed -n '200,290p' src/configs/schema.ts | nl -ba

Repository: knoxiboy/DoubtDesk

Length of output: 185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Inspect secureUserIdentifier assignment in action route =="
sed -n '1,120p' src/app/api/doubts/action/[id]/route.ts

echo
echo "== Locate anonymous_user usage on the client =="
rg -n 'anonymous_user' src

echo
echo "== Find where like/unlike request sends userName =="
rg -n "userName" src/app src/components src/lib src/pages src

echo
echo "== Inspect likes table schema definition =="
sed -n '210,270p' src/configs/schema.ts

Repository: knoxiboy/DoubtDesk

Length of output: 25667


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Inspect client like/unlike payload in DoubtCard.tsx =="
sed -n '1,120p' src/components/DoubtCard.tsx
echo
echo "== Inspect InfiniteDoubtFeed.tsx passing userName query param =="
sed -n '60,120p' src/components/InfiniteDoubtFeed.tsx
echo
echo "== Inspect doubts list endpoint using likesTable.userName (src/app/api/doubts/route.ts) =="
sed -n '120,190p' src/app/api/doubts/route.ts
echo
echo "== Inspect likesTable schema for unique constraint =="
sed -n '220,280p' src/configs/schema.ts

Repository: knoxiboy/DoubtDesk

Length of output: 12416


Fix inconsistent like/unlike identifiers after switching to Clerk email

src/app/api/doubts/action/[id]/route.ts stores/reads/deletes likes using secureUserIdentifier = email against likesTable.userName. But the client still sends userName = localStorage.getItem("anonymous_user") for like/unlike, and src/app/api/doubts/route.ts computes hasLiked by matching likesTable.userName to the userName query param (also from anonymous_user). If existing rows were keyed by anonymous_user, unlike will not find them; and new likes for the same doubt can be inserted again because likesTable enforces uniqueness on (userName, doubtId)—treating email and anonymous_user as different users—so the UI and doubtsTable.likes can diverge.

🤖 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/app/api/doubts/action/`[id]/route.ts around lines 55 - 56, The
like/unlike flow is inconsistent because action/[id]/route.ts uses
secureUserIdentifier = email while the client and src/app/api/doubts/route.ts
use the anonymous_user value as userName, causing mismatched lookups; fix this
by normalizing the identifier server-side: in action/[id]/route.ts set
secureUserIdentifier to the incoming userName (from the request body/query) when
it exists and fall back to email (Clerk) only if userName is absent, and ensure
the same normalization is applied in the doubts route that computes hasLiked
(matching likesTable.userName) so reads, inserts and deletes use the same key
(references: secureUserIdentifier, likesTable.userName, hasLiked logic in
src/app/api/doubts/route.ts).

@vedant7007 vedant7007 closed this Jun 1, 2026
@github-actions github-actions Bot added invalid This doesn't seem right and removed gssoc'26 GSSoC program issue level:intermediate Intermediate level task type:bug Bug fix size/m review-needed labels Jun 1, 2026
@knoxiboy knoxiboy reopened this Jun 6, 2026
@knoxiboy knoxiboy removed the invalid This doesn't seem right label Jun 6, 2026
@github-actions github-actions Bot added gssoc'26 GSSoC program issue level:intermediate Intermediate level task type:bug Bug fix size/m review-needed labels Jun 6, 2026
@knoxiboy knoxiboy closed this Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc'26 GSSoC program issue level:intermediate Intermediate level task type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Doubt like toggle has race condition — causes duplicate likes and negative like counts

2 participants