From f0ed4df2104c2908ba1aa7c1f8ac30cbb135c826 Mon Sep 17 00:00:00 2001 From: FuturMix Date: Sun, 14 Jun 2026 12:13:15 +0800 Subject: [PATCH] fix: use INSERT OR IGNORE to prevent vote race condition 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 --- apps/web/app/api/coupons/vote/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web/app/api/coupons/vote/route.ts b/apps/web/app/api/coupons/vote/route.ts index d2041f5..e0da6f4 100644 --- a/apps/web/app/api/coupons/vote/route.ts +++ b/apps/web/app/api/coupons/vote/route.ts @@ -15,7 +15,7 @@ export async function POST(req: NextRequest) { try { const db = getDb(); - // Check for duplicate + // Check for existing vote first (fast path for user feedback) const existing = await db.sql` SELECT id FROM coupon_votes WHERE coupon_id = ${id} AND voter_did = ${did} `; @@ -23,7 +23,10 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: 'already_voted' }, { status: 409 }); } - await db.sql`INSERT INTO coupon_votes (coupon_id, voter_did) VALUES (${id}, ${did})`; + // Atomic insert — UNIQUE(coupon_id, voter_did) constraint prevents + // duplicates from concurrent requests; OR IGNORE handles the race + // gracefully instead of throwing a constraint violation error + await db.sql`INSERT OR IGNORE INTO coupon_votes (coupon_id, voter_did) VALUES (${id}, ${did})`; await db.sql`UPDATE coupons SET votes = votes + 1 WHERE id = ${id}`; const rows = await db.sql`SELECT votes FROM coupons WHERE id = ${id}`;