Skip to content

Commit b93d28c

Browse files
FuturMixclaude
andauthored
fix: use INSERT OR IGNORE to prevent vote race condition (#26)
The vote handler uses SELECT to check for duplicates, then INSERT. Two concurrent requests can both pass the SELECT check. The UNIQUE constraint on (coupon_id, voter_did) catches this at the DB level, but the resulting constraint violation error returns a generic 500 instead of the expected 409 response. Use INSERT OR IGNORE so concurrent duplicate inserts are silently ignored instead of throwing errors, while keeping the SELECT check for user-friendly feedback on normal duplicate attempts. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5613469 commit b93d28c

1 file changed

Lines changed: 5 additions & 2 deletions

File tree

apps/web/app/api/coupons/vote/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ export async function POST(req: NextRequest) {
1515
try {
1616
const db = getDb();
1717

18-
// Check for duplicate
18+
// Check for existing vote first (fast path for user feedback)
1919
const existing = await db.sql`
2020
SELECT id FROM coupon_votes WHERE coupon_id = ${id} AND voter_did = ${did}
2121
`;
2222
if (existing.length > 0) {
2323
return NextResponse.json({ error: 'already_voted' }, { status: 409 });
2424
}
2525

26-
await db.sql`INSERT INTO coupon_votes (coupon_id, voter_did) VALUES (${id}, ${did})`;
26+
// Atomic insert — UNIQUE(coupon_id, voter_did) constraint prevents
27+
// duplicates from concurrent requests; OR IGNORE handles the race
28+
// gracefully instead of throwing a constraint violation error
29+
await db.sql`INSERT OR IGNORE INTO coupon_votes (coupon_id, voter_did) VALUES (${id}, ${did})`;
2730
await db.sql`UPDATE coupons SET votes = votes + 1 WHERE id = ${id}`;
2831

2932
const rows = await db.sql`SELECT votes FROM coupons WHERE id = ${id}`;

0 commit comments

Comments
 (0)