Skip to content

fix(affiliates): sync manual approval counts#317

Merged
ralyodio merged 2 commits into
profullstack:masterfrom
Jorel97:codex/fix-affiliate-counts-275
May 29, 2026
Merged

fix(affiliates): sync manual approval counts#317
ralyodio merged 2 commits into
profullstack:masterfrom
Jorel97:codex/fix-affiliate-counts-275

Conversation

@Jorel97
Copy link
Copy Markdown
Contributor

@Jorel97 Jorel97 commented May 29, 2026

Fixes #275.

Summary

  • read the current affiliate application status before manual moderation changes
  • increment affiliate_offers.total_affiliates only for non-approved -> approved transitions
  • decrement it only for approved -> rejected transitions, clamped at zero by a new atomic RPC
  • avoid double-counting repeated approvals and clear approved_at when an approved application is rejected
  • add route coverage for approval increment and rejection decrement

Tests

  • Not run locally: this Codex runtime has Node but no npm/pnpm executable or installed project dependencies.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR consolidates the affiliate application moderation flow into a single atomic PostgreSQL function (moderate_affiliate_application) that handles ownership verification, status transitions, approved_at management, and total_affiliates count adjustment in one transaction, replacing the previous multi-step approach that could desync on partial failure.

  • Migration creates moderate_affiliate_application as a SECURITY DEFINER function with REVOKE/GRANT restricted to service_role, preventing unauthenticated PostgREST access; count changes are idempotent (increment only on non-approved→approved, decrement only on approved→non-approved, clamped at zero).
  • Route (route.ts) is simplified to a single RPC call followed by a best-effort SELECT to enrich the response with the profiles join; the RPC error is properly propagated, but the enrichment SELECT's error is silently swallowed.
  • Tests add RPC mock wiring and two new cases covering the approve and reject paths, though the rejection test contains a vestigial updatePayload variable that is never populated.

Confidence Score: 5/5

Safe to merge — the atomic RPC cleanly eliminates the desync window, and the REVOKE/GRANT statements properly restrict access to the service role.

All moderation logic now lives in a single locked transaction; ownership checks, status transitions, count adjustments, and approved_at management are all atomic. The only remaining rough edges are an enrichment-SELECT whose error is silently dropped (producing a profileless response in rare failure scenarios) and a dead updatePayload variable in one test — neither affects correctness on the happy path.

The post-RPC enrichment SELECT in route.ts silently discards its error; worth a look if monitoring shows unexpectedly profileless responses in production.

Important Files Changed

Filename Overview
supabase/migrations/20260529195000_adjust_affiliate_offer_total_affiliates.sql Introduces atomic moderate_affiliate_application SECURITY DEFINER function that handles ownership verification, status transition, count adjustment (clamped at 0), and approved_at clearing in one transaction; includes proper REVOKE/GRANT so only service_role can invoke it.
src/app/api/affiliates/offers/[id]/applications/route.ts PATCH handler now delegates all ownership/status/count logic to the new atomic RPC; a follow-up SELECT fetches the profile join, but its error is silently discarded so a transient DB failure after a successful RPC would return a profileless response without surfacing the problem.
src/app/api/affiliates/offers/[id]/applications/route.test.ts Adds RPC mock and two new test cases for approve/reject paths; a vestigial updatePayload variable in the rejection test is declared but never captures a value, so expect(updatePayload).toBeUndefined() is a tautological assertion that adds no coverage.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as PATCH /api/affiliates/offers/[id]/applications
    participant RPC as moderate_affiliate_application (DB)
    participant Select as affiliate_applications SELECT
    participant Notify as notifications INSERT

    Client->>Route: "{ application_id, action }"
    Route->>RPC: p_offer_id, p_application_id, p_seller_id, p_status
    Note over RPC: 1. Lock offer row (FOR UPDATE)<br/>2. Verify seller_id<br/>3. Lock application row (FOR UPDATE)<br/>4. Compute delta (idempotent)<br/>5. UPDATE application + approved_at<br/>6. UPDATE total_affiliates (GREATEST(0,…))
    RPC-->>Route: updated application row (no profiles join)
    Route->>Select: "SELECT *, profiles join WHERE id=… AND offer_id=…"
    Select-->>Route: enriched row (or null on failure, error silently dropped)
    Route->>Notify: INSERT notification for affiliate
    Notify-->>Route: ok
    Route-->>Client: "{ application: enriched || raw }"
Loading

Reviews (2): Last reviewed commit: "fix(affiliates): moderate counts atomica..." | Re-trigger Greptile

Comment thread supabase/migrations/20260529195000_adjust_affiliate_offer_total_affiliates.sql Outdated
Comment thread src/app/api/affiliates/offers/[id]/applications/route.ts Outdated
@ralyodio ralyodio merged commit e1af10e into profullstack:master May 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep affiliate offer counts in sync on manual approval changes

2 participants