Skip to content

fix(referrals): normalize invite email dedupe#280

Open
sevencat2004 wants to merge 2 commits into
profullstack:masterfrom
sevencat2004:fix/referral-normalize-existing-invites
Open

fix(referrals): normalize invite email dedupe#280
sevencat2004 wants to merge 2 commits into
profullstack:masterfrom
sevencat2004:fix/referral-normalize-existing-invites

Conversation

@sevencat2004
Copy link
Copy Markdown

Summary

  • normalize Invite Friends emails once before rate-limit counting, duplicate lookup, inserts, and email sends
  • dedupe repeated emails within the same request after trimming/lowercasing
  • add regression coverage for an existing invited email submitted again with whitespace/case changes

Fixes #279

Payment

  • Solana wallet: Dy4yMkjCfupxaURt6iTMUrxqSDEmAJPPkKF66QahxJZD

Test plan

  • npx.cmd vitest run src/app/api/referrals/route.test.ts
  • git diff --check -- src/app/api/referrals/route.ts src/app/api/referrals/route.test.ts

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR fixes invite email deduplication by normalizing (trim + lowercase) the raw email list into a Set once, before any rate-limit checks, duplicate lookups, inserts, or sends — eliminating the previous gap where " Existing@Test.COM " could slip past the already-invited guard. A targeted regression test covers the multi-variant case.

  • Single normalization point (route.ts lines 82-88): Array.from(new Set(emails.map(e => e.trim().toLowerCase()).filter(…))) replaces the old pattern where validEmails was un-normalized and a separate normalizedEmails variable was used only for the duplicate query.
  • Insert cleanup (route.ts line 166): referred_email: email is now used directly since every value in newValidEmails is already canonical.
  • Test baseline (route.test.ts): mockReferralInviteEmail and mockSendEmail are now initialized in the POST beforeEach, making each test independent of prior mock state.

Confidence Score: 5/5

The change is narrowly scoped to the normalization pipeline and carries a direct regression test; no data-loss or auth paths are touched.

All downstream operations (rate-limit count, DB duplicate query, insert, email send) now use the same canonical address produced by a single trim().toLowerCase() + Set pass. The logic is straightforward and the new test exercises the exact failure mode the fix addresses.

No files require special attention.

Important Files Changed

Filename Overview
src/app/api/referrals/route.ts Email normalization (trim + lowercase) and in-request deduplication via Set now happen once before rate-limit counts, duplicate lookup, inserts, and sends; the old separate normalizedEmails variable is gone and the insert row no longer re-normalizes.
src/app/api/referrals/route.test.ts Adds a regression test that sends the same address in three whitespace/case variations and an already-existing address; asserts only the single new canonical form is inserted and emailed. Also moves mockReferralInviteEmail/mockSendEmail setup into the POST beforeEach so each test starts with a predictable baseline.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/referrals] --> B[Auth check]
    B --> C[Parse emails array]
    C --> D{Validate: array,\nnon-empty, ≤20}
    D -- invalid --> E[400]
    D -- ok --> F["Normalize once:\ntrim + lowercase → Set → filter regex\n= validEmails"]
    F --> G{validEmails empty?}
    G -- yes --> H[400 No valid emails]
    G -- no --> I[Rate-limit check\nhourly / daily\nusing validEmails.length]
    I -- exceeded --> J[429]
    I -- ok --> K["DB: .in(referred_email, validEmails)\n= existingInvites"]
    K --> L["newValidEmails =\nvalidEmails - alreadyInvited"]
    L --> M{newValidEmails empty?}
    M -- yes --> N[400 Already invited]
    M -- no --> O[Insert referralRows]
    O --> P[Send emails]
    P --> Q[200 response]
Loading

Reviews (2): Last reviewed commit: "chore(referrals): remove redundant invit..." | Re-trigger Greptile

Comment thread src/app/api/referrals/route.ts
@sevencat2004
Copy link
Copy Markdown
Author

sevencat2004 commented May 28, 2026

Follow-up pushed in 1a6a082: removed the redundant insert-row normalization that Greptile flagged. newValidEmails is already the canonical trimmed/lowercased source from the shared normalization pipeline, so inserts now use that value directly.\n\nValidation run locally:\n- corepack pnpm test -- src/app/api/referrals/route.test.ts

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.

Invite Friends duplicate check misses normalized existing emails

1 participant