[codex] Fix affiliate approval counts#276
Conversation
Greptile SummaryThis PR replaces a non-atomic status-update + counter-adjustment pair with a single Postgres RPC (
Confidence Score: 5/5Safe to merge — the atomic RPC correctly handles concurrent approvals and the privilege restriction prevents client-side bypass. The two main issues from the previous review (silent RPC errors and unguarded SECURITY DEFINER exposure) are both resolved. The SQL uses FOR UPDATE locking and idempotent counter guards so concurrent calls cannot double-count. The remaining open item — the VOID return type requiring a post-commit SELECT — is a reliability nuance on an already-committed write, not a correctness defect on the happy path. The migration SQL is the most load-bearing file; worth a quick read to confirm the REVOKE/GRANT block was applied to the correct function signature in your target environment. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Route as PATCH /api/…/applications
participant Supabase as Supabase (service_role)
participant DB as Postgres
Client->>Route: "PATCH {application_id, action}"
Route->>Supabase: from("affiliate_offers").select().eq().single()
Supabase->>DB: SELECT id, seller_id FROM affiliate_offers
DB-->>Route: offer (ownership check)
alt "seller_id !== auth.user.id"
Route-->>Client: 404 Not found or not authorized
end
Route->>Supabase: "rpc("update_affiliate_application_status", {p_application_id, p_offer_id, p_status})"
Supabase->>DB: BEGIN — SELECT … FOR UPDATE on affiliate_applications
DB->>DB: UPDATE affiliate_applications SET status, approved_at, updated_at
DB->>DB: UPDATE affiliate_offers SET total_affiliates ± 1 (if transition warrants)
DB-->>Supabase: COMMIT
alt RPC error
Route-->>Client: "400 {error: statusError.message}"
end
Route->>Supabase: from("affiliate_applications").select().eq().eq().single()
Supabase->>DB: "SELECT * FROM affiliate_applications WHERE id AND offer_id"
DB-->>Route: updated application row
Route->>Supabase: "from("notifications").insert({...}) [fire-and-forget]"
Route-->>Client: "200 {application}"
Reviews (6): Last reviewed commit: "Assert affiliate rejection notification" | Re-trigger Greptile |
Summary
affiliate_offers.total_affiliatesin syncservice_roleso callers cannot bypass the route-level seller ownership checkVerification
./node_modules/.bin/vitest.cmd run src/app/api/affiliates/offers/[id]/applications/route.test.ts./node_modules/.bin/tsc.cmd --noEmitgit diff --check -- src/app/api/affiliates/offers/[id]/applications/route.ts src/app/api/affiliates/offers/[id]/applications/route.test.ts supabase/migrations/20260527161500_update_affiliate_application_status.sql