Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Jul 27, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive fraud detection and risk management system, including detection of self-referrals, Google Ads clicks, and disposable emails.
    • Added fraud event tracking, review, and resolution capabilities with detailed event histories.
    • Implemented new dashboard views, statistics, and filterable tables for fraud events.
    • Enabled marking fraud events as safe or banned, with associated partner management actions and notifications.
    • Added API endpoints and hooks for fraud event data retrieval and counts.
    • Enhanced sidebar and navigation with fraud event badges and review access.
  • Bug Fixes

    • Improved handling and detection of generic and disposable email domains.
  • Refactor

    • Streamlined partner ban/unban logic into dedicated functions for consistency and maintainability.
  • Chores

    • Updated database schemas to support fraud event relations and statuses.
    • Added new utility functions and schemas for fraud detection and email handling.
    • Updated dependencies and improved code structure for scalability.

@vercel
Copy link
Contributor

vercel bot commented Jul 27, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Aug 8, 2025 6:39am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 27, 2025

Walkthrough

This update introduces a comprehensive fraud detection and risk management system across the application. It adds new Prisma schema models and fields for tracking fraud events, implements detection logic for self-referrals, Google Ads clicks, and disposable emails, and integrates asynchronous fraud detection and recording into partner-related workflows. The frontend gains new APIs, React components, hooks, and UI elements for displaying, filtering, and managing fraud events, including status badges, modals for marking events as safe or banned, and a detailed risk review sheet.

Changes

Cohort / File(s) Change Summary
Fraud Detection Logic and Utilities
apps/web/lib/analytics/fraud/detect-fraud-events.ts, apps/web/lib/analytics/fraud/is-google-ads-click.ts, apps/web/lib/analytics/fraud/is-self-referral.ts, apps/web/lib/analytics/fraud/is-disposable-email.ts, apps/web/lib/analytics/fraud/detect-and-record-fraud.ts, apps/web/lib/emails.ts, packages/utils/src/functions/email.ts, packages/utils/src/functions/index.ts
Introduces modular fraud detection utilities for self-referral, Google Ads click, and disposable email detection. Adds helper for destructuring emails and expands generic email domain checks. Implements a central fraud event detection and recording workflow.
Backend Fraud Event Models and Schema
packages/prisma/schema/fraud.prisma, packages/prisma/schema/commission.prisma, packages/prisma/schema/customer.prisma, packages/prisma/schema/link.prisma, packages/prisma/schema/partner.prisma, packages/prisma/schema/program.prisma, packages/prisma/schema/schema.prisma, packages/prisma/client.ts
Adds FraudEvent model and related enums, fields, and relations to Prisma schemas for tracking fraud events. Updates Commission model with fraud event linkage and status. Expands user and program models for fraud event associations.
Fraud Event API Endpoints
apps/web/app/(ee)/api/fraud-events/route.ts, apps/web/app/(ee)/api/fraud-events/count/route.ts
Adds new API endpoints for querying and counting fraud events with support for filtering, grouping, and pagination.
Fraud Event Actions and Partner Ban/Unban
apps/web/lib/actions/partners/mark-fraud-event-banned.ts, apps/web/lib/actions/partners/mark-fraud-event-safe.ts, apps/web/lib/actions/partners/ban-partner.ts, apps/web/lib/actions/partners/unban-partner.ts, apps/web/lib/api/partners/ban-partner.ts, apps/web/lib/api/partners/unban-partner.ts
Implements actions and APIs for marking fraud events as safe or banned, and for banning/unbanning partners. Refactors ban/unban logic into reusable server-side functions.
Fraud Detection Integration in Workflows
apps/web/lib/api/conversions/track-lead.ts, apps/web/lib/api/conversions/track-sale.ts, apps/web/lib/integrations/shopify/create-sale.ts, apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts, apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts
Integrates fraud detection and recording into lead, sale, and external integration workflows, ensuring fraud checks run asynchronously alongside partner notifications.
Fraud Event Zod Schemas and Types
apps/web/lib/types.ts, apps/web/lib/zod/schemas/fraud-events.ts, apps/web/lib/zod/schemas/commissions.ts, apps/web/lib/zod/schemas/users.ts, apps/web/lib/zod/schemas/payouts.ts
Adds and updates Zod schemas and TypeScript types for fraud events, commissions, and users to support new fraud event data structures and validation.
Fraud Event React Components and Hooks
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-stats.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/use-fraud-event-filters.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/page-client.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/page.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/overview-tasks.tsx, apps/web/ui/partners/mark-fraud-event-banned-modal.tsx, apps/web/ui/partners/mark-fraud-event-safe-modal.tsx, apps/web/ui/partners/risk-review-sheet.tsx, apps/web/lib/swr/use-fraud-events.ts, apps/web/lib/swr/use-fraud-events-count.ts
Adds React components and hooks for displaying fraud event statistics, tables, filters, modals for marking events safe/banned, and a detailed risk review sheet. Implements SWR hooks for fraud event data fetching.
UI Badges and Table Enhancements
apps/web/ui/partners/commission-status-badges.tsx, apps/web/ui/partners/fraud-event-status-badges.ts, apps/web/ui/customers/customer-row-item.tsx, apps/web/ui/partners/commission-row-menu.tsx
Adds new status badges for commissions and fraud events, updates customer and commission row components to support fraud review navigation and conditional icon rendering.
Sidebar and Navigation Updates
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
Integrates pending fraud event counts into the sidebar navigation under the "Partner Program" section.
Audit Log Schema Updates
apps/web/lib/api/audit-logs/schemas.ts
Adds audit log support for fraud event status changes (marked safe/banned).
Supporting Utilities and Scripts
apps/web/lib/api/utils.ts, apps/web/lib/api/create-id.ts, apps/web/scripts/get-top-email-domains.ts
Adds utility for user agent parsing, extends allowed ID prefixes, and introduces a script for analyzing top email domains.
Minor and Maintenance Changes
apps/web/app/(ee)/api/partners/[partnerId]/route.ts, apps/web/ui/partners/archive-partner-modal.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx, apps/web/lib/middleware/utils/get-final-url.ts, apps/web/package.json, packages/utils/src/functions/index.ts
Small refactors, dependency additions, comment removals, and maintenance updates.

Sequence Diagram(s)

Fraud Detection and Recording Flow

sequenceDiagram
    participant Workflow (Lead/Sale/Integration)
    participant detectAndRecordFraud
    participant DB (Prisma)
    participant detectFraudEvents
    participant isSelfReferral/isGoogleAdsClick/isDisposableEmail

    Workflow->>detectAndRecordFraud: Trigger with partner, customer, click, commission info
    detectAndRecordFraud->>DB: Fetch program enrollment & partner user IP
    alt Enrollment is trusted
        detectAndRecordFraud-->>Workflow: Skip fraud detection
    else Not trusted
        detectAndRecordFraud->>detectFraudEvents: Run fraud checks
        detectFraudEvents->>isSelfReferral: Check self-referral
        detectFraudEvents->>isGoogleAdsClick: Check Google Ads click
        detectFraudEvents->>isDisposableEmail: Check disposable email
        detectFraudEvents-->>detectAndRecordFraud: Return fraud events
        alt Fraud events found
            detectAndRecordFraud->>DB: Upsert FraudEvent, link commission, set status "held"
        else No fraud events
            detectAndRecordFraud-->>Workflow: Log and exit
        end
    end
Loading

Fraud Event Review and Resolution (Frontend)

sequenceDiagram
    participant User
    participant FraudEventTable
    participant RiskReviewSheet
    participant markFraudEventSafe/BannedModal
    participant API (mark-fraud-event-safe/banned)
    participant DB

    User->>FraudEventTable: Click "Review risk"
    FraudEventTable->>RiskReviewSheet: Open with fraud event details
    User->>RiskReviewSheet: Click "Mark as Safe" or "Ban Partner"
    RiskReviewSheet->>markFraudEventSafe/BannedModal: Open modal
    User->>markFraudEventSafe/BannedModal: Confirm action
    markFraudEventSafe/BannedModal->>API: Submit resolution
    API->>DB: Update FraudEvent status, commissions, enrollment
    API-->>markFraudEventSafe/BannedModal: Success/Failure
    markFraudEventSafe/BannedModal-->>RiskReviewSheet: Close modal, refresh data
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Poem

🐇
A sniff of fraud, a bunny detects,
With clever code, each risk it inspects.
From self-referrals to links gone askew,
Disposable emails—caught by the crew!
Now dashboards gleam, with badges and flair,
Partners reviewed with vigilant care.
Hop, hop, hooray—fraud’s caught unaware!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ccd060 and c0af47c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • apps/web/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fraud

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@steven-tey steven-tey marked this pull request as draft July 27, 2025 06:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
apps/web/lib/analytics/fraud/is-google-ads-click.ts (1)

3-15: Simplify the boolean return logic.

The function logic is correct, but the explicit if-else structure can be simplified for better readability.

 export const isGoogleAdsClick = (url: string) => {
   const urlObj = getUrlObjFromString(url);
 
   if (!urlObj) {
     return false;
   }
 
-  if (urlObj.searchParams.has("gclid")) {
-    return true;
-  }
-
-  return false;
+  return urlObj.searchParams.has("gclid");
 };
apps/web/scripts/get-top-email-domains.ts (1)

13-18: Optimize database query by removing unused fields.

The script only uses the email field for domain analysis, but also selects name and createdAt which are not used, potentially impacting performance.

 const users = await prisma.user.findMany({
   select: {
     email: true,
-    name: true,
-    createdAt: true,
   },
   skip,
   take: CHUNK_SIZE,
 });
apps/web/lib/analytics/fraud/is-self-referral.ts (1)

203-213: Consider implementing or removing disposable email check.

There's commented-out code for checking disposable email domains. Either implement this feature completely or remove the code to avoid confusion:

-  // Check for disposable email domains
-  // const { domain: customerDomain } = destructureEmail(customer.email);
-  // const isCustomerDisposable = await redis.sismember(
-  //   "disposableEmailDomains",
-  //   customerDomain,
-  // );
-
-  // if (isCustomerDisposable) {
-  //   confidence += 0.3;
-  //   reasons.push("Customer email uses disposable domain");
-  // }
+  // Check for disposable email domains
+  const { domain: customerDomain } = destructureEmail(customer.email);
+  const isCustomerDisposable = await redis.sismember(
+    "disposableEmailDomains",
+    customerDomain,
+  );
+
+  if (isCustomerDisposable) {
+    confidence += 0.3;
+    reasons.push("Customer email uses disposable domain");
+  }

Note: This would require importing redis and ensuring the disposable domains set is populated.

Would you like help implementing the disposable email detection feature?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1229fd8 and 46bf37a.

📒 Files selected for processing (10)
  • apps/web/lib/analytics/fraud/is-google-ads-click.ts (1 hunks)
  • apps/web/lib/analytics/fraud/is-self-referral.ts (1 hunks)
  • apps/web/lib/api/conversions/track-lead.ts (2 hunks)
  • apps/web/lib/emails.ts (1 hunks)
  • apps/web/scripts/get-top-email-domains.ts (1 hunks)
  • packages/prisma/schema/customer.prisma (1 hunks)
  • packages/prisma/schema/fraud.prisma (1 hunks)
  • packages/prisma/schema/link.prisma (1 hunks)
  • packages/prisma/schema/partner.prisma (1 hunks)
  • packages/prisma/schema/program.prisma (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
packages/prisma/schema/link.prisma (1)

Learnt from: devkiran
PR: #2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

apps/web/lib/api/conversions/track-lead.ts (2)

Learnt from: devkiran
PR: #2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Learnt from: devkiran
PR: #2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
apps/web/lib/emails.ts (1)

1-24: Excellent refactoring that improves maintainability and performance.

The refactoring from multiple hardcoded endsWith checks to an array-based lookup provides several benefits:

  1. Maintainability: Centralized domain list is easier to update
  2. Performance: Single some() call is more efficient than multiple endsWith checks
  3. Reusability: Exported genericEmailDomains array can be used elsewhere
  4. Coverage: Expanded domain list improves fraud detection accuracy

The implementation correctly handles the domain matching logic.

packages/prisma/schema/partner.prisma (1)

58-58: Schema change approved: FraudEvent model and relations verified

The Partner model’s new fraudEvents FraudEvent[] field correctly pairs with the partner Partner @relation(fields: [partnerId], references: [id]) in the FraudEvent model. All relation fields and indexes in packages/prisma/schema/fraud.prisma are properly defined.

  • No further changes needed.
  • The one-to-many relationship is fully established.
packages/prisma/schema/program.prisma (1)

64-64: Schema relation correctly implemented.

The fraudEvents relation field is properly added to the Program model, maintaining consistency with the Partner model changes and supporting comprehensive fraud event tracking across the system.

packages/prisma/schema/customer.prisma (1)

25-26: LGTM! Schema relation properly configured.

The fraudEvents relation field correctly establishes a one-to-many relationship from Customer to FraudEvent, consistent with the fraud detection infrastructure being introduced.

packages/prisma/schema/link.prisma (1)

83-83: LGTM! Relation field properly added.

The fraudEvents relation correctly establishes a one-to-many relationship from Link to FraudEvent, consistent with the fraud tracking requirements.

apps/web/lib/api/conversions/track-lead.ts (1)

1-2: Fraud detection utilities properly imported.

The imports for isGoogleAdsClick and isSelfReferral utilities are correctly added to support fraud detection functionality.

packages/prisma/schema/fraud.prisma (1)

1-5: Fraud event types properly defined.

The FraudEventType enum correctly captures the three types of fraud being detected in the system.

apps/web/lib/analytics/fraud/is-self-referral.ts (1)

82-88: Verify domain comparison logic for generic email providers.

The condition appears to give higher confidence when domains match for non-generic providers. However, matching domains for company emails (e.g., @company.com) could be legitimate referrals between colleagues:

 if (
   customerDomain === partnerDomain &&
-  !genericEmailDomains.includes(customerDomain)
+  genericEmailDomains.includes(customerDomain)
 ) {
-  score += 0.3;
-  reasons.push("Identical email domains");
+  score += 0.1;
+  reasons.push("Same generic email provider");
 }

Alternatively, if the current logic is intentional, consider adding a comment explaining why non-generic matching domains indicate fraud.

devkiran added 2 commits July 29, 2025 14:06
…information in API response, updating UI components to display fraud events with new attributes, and implementing a script for bulk fraud event creation.
…, enhancing UI components for better display of fraud event details and integrating new properties for improved user experience.
devkiran added 2 commits July 29, 2025 18:35
Introduces a modal UI for banning a partner in response to a fraud event, allowing selection of a ban reason and notification option. Updates the backend action to accept and store the ban reason, and integrates the modal into the risk review sheet for pending fraud events.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/web/ui/partners/risk-review-sheet.tsx (2)

46-48: Handle error state when fetching partner data

The usePartner hook's error field is not destructured or handled, causing the component to render nothing on fetch failure and confuse users.


349-354: Handle undefined commission amounts

The helAmount calculation doesn't explicitly handle the case where fraudEvent.commissions is undefined or null, which could lead to runtime errors.

🧹 Nitpick comments (3)
apps/web/scripts/fraud-events.ts (1)

6-6: Remove or explain the unused variable.

The variable i = 3 appears to be used only for email generation but its purpose and specific value aren't clear.

Consider making this more explicit:

-  const i = 3;
+  const testCaseNumber = 3; // or remove if not needed for multiple test cases
apps/web/ui/partners/risk-review-sheet.tsx (2)

342-523: Consider extracting FraudEventCard to a separate file.

The FraudEventCard component is quite substantial (180+ lines) and is used in multiple contexts. Extracting it to a separate file would improve maintainability and reusability.

Create a new file apps/web/ui/partners/fraud-event-card.tsx:

export function FraudEventCard({ fraudEvent }: { fraudEvent: FraudEvent }) {
  // Move the entire FraudEventCard implementation here
}

Then import it in this file:

+import { FraudEventCard } from "./fraud-event-card";

487-499: Improve conditional rendering logic.

The condition helAmount && helAmount > 0 could be simplified and made more explicit.

-            {helAmount && helAmount > 0 ? (
+            {(helAmount ?? 0) > 0 && (
               <div>
                 <h3 className="text-sm font-medium text-neutral-900">
                   Commission hold
                 </h3>
                 <span className="text-sm font-medium text-neutral-500">
                   {currencyFormatter(helAmount / 100, {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
                   })}
                 </span>
               </div>
-            ) : null}
+            )}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0966e5 and f9612f5.

📒 Files selected for processing (13)
  • apps/web/app/(ee)/api/fraud-events/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (1 hunks)
  • apps/web/lib/actions/partners/ban-partner.ts (2 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts (1 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts (1 hunks)
  • apps/web/lib/actions/partners/unban-partner.ts (2 hunks)
  • apps/web/lib/api/partners/ban-partner.ts (1 hunks)
  • apps/web/lib/api/partners/unban-partner.ts (1 hunks)
  • apps/web/lib/partners/constants.ts (0 hunks)
  • apps/web/lib/zod/schemas/fraud-events.ts (1 hunks)
  • apps/web/scripts/fraud-events.ts (1 hunks)
  • apps/web/ui/partners/risk-review-sheet.tsx (1 hunks)
  • packages/prisma/schema/fraud.prisma (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/lib/partners/constants.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/web/app/(ee)/api/fraud-events/route.ts
  • apps/web/lib/actions/partners/ban-partner.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts
  • apps/web/lib/api/partners/ban-partner.ts
  • apps/web/lib/zod/schemas/fraud-events.ts
  • packages/prisma/schema/fraud.prisma
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/scripts/fraud-events.ts (1)
apps/web/lib/api/create-id.ts (1)
  • createId (57-66)
apps/web/lib/actions/partners/unban-partner.ts (1)
apps/web/lib/api/partners/unban-partner.ts (1)
  • unbanPartner (10-99)
🔇 Additional comments (3)
apps/web/lib/actions/partners/unban-partner.ts (1)

32-37: LGTM! Clean separation of concerns.

The refactoring successfully delegates the unbanning logic to a dedicated API function, making the action handler cleaner and more maintainable. The validation logic remains intact while the business logic is properly encapsulated.

apps/web/lib/api/partners/unban-partner.ts (2)

26-67: Excellent transaction design and error handling.

The implementation demonstrates several best practices:

  • Uses Prisma transaction for atomicity across multiple entity updates
  • Properly resets all related data (links, enrollment, commissions, payouts)
  • Good use of spread operator for common where conditions

69-98: Proper async operation handling.

The use of waitUntil with Promise.allSettled is appropriate for non-critical operations like cache clearing and audit logging. This ensures the main operation doesn't fail if these secondary operations encounter issues.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/web/scripts/fraud-events.ts (1)

30-31: Add error handling and logging for better operational clarity.

The script lacks error handling and feedback mechanisms, which could make debugging difficult if database operations fail.

Consider this improvement for better error handling and logging:

-main();
+main()
+  .then(() => {
+    console.log("✅ Fraud event script completed successfully");
+    process.exit(0);
+  })
+  .catch((error) => {
+    console.error("❌ Fraud event script failed:", error);
+    process.exit(1);
+  });

Additionally, consider wrapping the database operations in a transaction to ensure atomicity:

 async function main() {
+  return await prisma.$transaction(async (tx) => {
     const customer = await tx.customer.create({
       // ... existing data
     });

     await tx.fraudEvent.create({
       // ... existing data
     });
+  });
 }
🧹 Nitpick comments (3)
apps/web/lib/api/partners/unban-partner.ts (1)

69-98: Consider adding error logging for failed async operations.

While using Promise.allSettled is good for preventing cascading failures, consider logging errors when cache invalidation or audit logging fails to improve observability.

-      await Promise.allSettled([
+      const results = await Promise.allSettled([
         linkCache.deleteMany(links),
 
         recordAuditLog({
           workspaceId: workspace.id,
           programId: program.id,
           action: "partner.unbanned",
           description: `Partner ${partner.id} unbanned`,
           actor: user,
           targets: [
             {
               type: "partner",
               id: partner.id,
               metadata: partner,
             },
           ],
         }),
       ]);
+
+      results.forEach((result, index) => {
+        if (result.status === 'rejected') {
+          const operation = index === 0 ? 'cache invalidation' : 'audit logging';
+          console.error(`Failed ${operation} during partner unban:`, result.reason);
+        }
+      });
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (2)

298-301: Enhance email subject with fraud event context.

Consider including more context in the email subject to help partners identify which fraud event is being discussed.

                  window.open(
-                    `mailto:${fraudEvent.partner.email}?subject=Question about fraud event`,
+                    `mailto:${fraudEvent.partner.email}?subject=Question about fraud event - ${formatDate(fraudEvent.createdAt, { month: "short" })}`,
                    "_blank",
                  );

351-363: Consider refactoring fraud reason collection for better maintainability.

The current approach manually checks each fraud type. Consider iterating over fraud types to make it easier to add new types in the future.

 function FraudEventReasons({ fraudEvent }: { fraudEvent: FraudEvent }) {
-  const reasons: string[] = [];
-
-  if (fraudEvent.selfReferral) {
-    reasons.push(FRAUD_EVENT_TYPES.selfReferral.label);
-  }
-
-  if (fraudEvent.googleAdsClick) {
-    reasons.push(FRAUD_EVENT_TYPES.googleAdsClick.label);
-  }
-
-  if (fraudEvent.disposableEmail) {
-    reasons.push(FRAUD_EVENT_TYPES.disposableEmail.label);
-  }
+  const reasons = Object.entries(FRAUD_EVENT_TYPES)
+    .filter(([key]) => fraudEvent[key as keyof FraudEvent])
+    .map(([_, type]) => type.label);

This approach automatically includes all fraud types defined in FRAUD_EVENT_TYPES without manual updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0966e5 and f9612f5.

📒 Files selected for processing (13)
  • apps/web/app/(ee)/api/fraud-events/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (1 hunks)
  • apps/web/lib/actions/partners/ban-partner.ts (2 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts (1 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts (1 hunks)
  • apps/web/lib/actions/partners/unban-partner.ts (2 hunks)
  • apps/web/lib/api/partners/ban-partner.ts (1 hunks)
  • apps/web/lib/api/partners/unban-partner.ts (1 hunks)
  • apps/web/lib/partners/constants.ts (0 hunks)
  • apps/web/lib/zod/schemas/fraud-events.ts (1 hunks)
  • apps/web/scripts/fraud-events.ts (1 hunks)
  • apps/web/ui/partners/risk-review-sheet.tsx (1 hunks)
  • packages/prisma/schema/fraud.prisma (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/lib/partners/constants.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/web/lib/actions/partners/ban-partner.ts
  • apps/web/app/(ee)/api/fraud-events/route.ts
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts
  • apps/web/lib/api/partners/ban-partner.ts
  • apps/web/lib/zod/schemas/fraud-events.ts
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts
  • apps/web/ui/partners/risk-review-sheet.tsx
  • packages/prisma/schema/fraud.prisma
🔇 Additional comments (4)
apps/web/lib/actions/partners/unban-partner.ts (1)

3-3: Good refactoring to centralize unban logic.

The extraction of the unban logic into a dedicated function improves code organization and maintainability. This follows the DRY principle and aligns with the similar pattern used for banning partners.

Also applies to: 32-37

apps/web/lib/api/partners/unban-partner.ts (1)

26-67: Well-structured atomic transaction for unban operations.

The transaction correctly handles all necessary updates: clearing link expirations, updating enrollment status, and reverting commission/payout statuses. This ensures data consistency across all affected entities.

apps/web/scripts/fraud-events.ts (2)

1-3: LGTM!

The imports are well-structured and appropriate for a database script. Using dotenv-flow/config ensures environment variables are loaded correctly.


16-27: Verify hardcoded foreign key references in fraud event creation

The script didn’t find any occurrences of these IDs elsewhere in the codebase, so please confirm they exist in the target database and won’t cause foreign-key constraint violations.

• File: apps/web/scripts/fraud-events.ts (lines 16–27)
– programId: prog_1K1QC1VPXKT6CP80KTDV6SMBR
– partnerId: pn_1K1QE594RDF9MGE3YYXPEVP4H
– linkId: link_1K1QE5BG9FWQHN2C4BVX33J13

Suggested actions:

  • Manually verify that each referenced record exists in your database before running this script.
  • Consider making these IDs configurable (e.g., via environment variables or a lookup table) and adding error handling around the prisma.fraudEvent.create call to guard against missing records.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (1)

133-137: Hold amount calculation properly handles null/undefined commissions

The implementation correctly uses optional chaining and nullish coalescing to handle cases where commissions might be null or undefined, returning 0 as the default value.

apps/web/ui/partners/risk-review-sheet.tsx (2)

46-48: Add error handling for partner data fetching

The usePartner hook returns an error field that should be handled to provide better user experience when the fetch fails.

- const { partner, loading: isLoadingPartner } = usePartner({
-   partnerId: fraudEvent.partner.id,
- });
+ const { partner, loading: isLoadingPartner, error: partnerError } = usePartner({
+   partnerId: fraudEvent.partner.id,
+ });

Then add error handling after the loading check:

  {isLoadingPartner ? (
    // ... loading skeleton
  ) : (
+   partnerError ? (
+     <div className="text-sm text-red-600">Failed to load partner data</div>
+   ) : (
    partner && <PartnerInfoSection partner={partner} />
+   )
  )}

349-354: Add null safety to commission amount calculation

The reduce operation could fail if fraudEvent.commissions is null or undefined.

  const helAmount = useMemo(() => {
-   return fraudEvent.commissions?.reduce(
+   return (fraudEvent.commissions ?? []).reduce(
      (acc, commission) => acc + commission.earnings,
      0,
    );
  }, [fraudEvent.commissions]);
🧹 Nitpick comments (5)
apps/web/scripts/fraud-events.ts (3)

6-6: Remove or clarify the hardcoded variable.

The variable i = 3 appears to be a remnant from a loop or batch operation. If this script is intended for one-time execution, consider removing this variable and using a more descriptive identifier in the email.

-  const i = 3;
+  // Using a descriptive identifier for the test email

8-14: Consider parameterizing the hardcoded project ID.

The customer creation uses a hardcoded projectId. For a reusable script, consider making this configurable via environment variables or command-line arguments.

  const customer = await prisma.customer.create({
    data: {
      id: createId({ prefix: "cus_" }),
-      email: `thomas+${i}@example.com`,
-      projectId: "clrei1gld0002vs9mzn93p8ik",
+      email: `[email protected]`,
+      projectId: process.env.PROJECT_ID || "clrei1gld0002vs9mzn93p8ik",
    },
  });

16-27: Add error handling and consider more realistic test data.

The fraud event creation has several areas for improvement:

  1. All fraud flags are set to true, which may not represent realistic test scenarios
  2. Hardcoded IDs make the script less flexible
  3. No error handling if the creation fails
- await prisma.fraudEvent.create({
+ const fraudEvent = await prisma.fraudEvent.create({
    data: {
      id: createId({ prefix: "fraud_" }),
-      programId: "prog_1K1QC1VPXKT6CP80KTDV6SMBR",
-      partnerId: "pn_1K1QE594RDF9MGE3YYXPEVP4H",
+      programId: process.env.PROGRAM_ID || "prog_1K1QC1VPXKT6CP80KTDV6SMBR",
+      partnerId: process.env.PARTNER_ID || "pn_1K1QE594RDF9MGE3YYXPEVP4H",
      customerId: customer.id,
-      linkId: "link_1K1QE5BG9FWQHN2C4BVX33J13",
-      selfReferral: true,
-      googleAdsClick: true,
-      disposableEmail: true,
+      linkId: process.env.LINK_ID || "link_1K1QE5BG9FWQHN2C4BVX33J13",
+      selfReferral: false, // More realistic default
+      googleAdsClick: false,
+      disposableEmail: true, // Keep one flag true for testing
    },
  });
+ 
+ console.log(`Created fraud event: ${fraudEvent.id}`);
apps/web/lib/api/partners/unban-partner.ts (2)

69-98: Good use of waitUntil for non-blocking side effects

The implementation properly handles cache invalidation and audit logging without blocking the response. Using Promise.allSettled ensures both operations run independently.

Consider adding error logging for failed operations since allSettled will silently continue even if cache invalidation or audit logging fails.


10-99: Consider adding explicit error handling

While Prisma handles transaction rollback automatically, consider adding explicit error handling for better observability:

export const unbanPartner = async ({
  workspace,
  program,
  partner,
  user,
}: {
  workspace: Pick<Project, "id">;
  program: Pick<Program, "id" | "name" | "supportEmail">;
  partner: Pick<Partner, "id" | "name" | "email">;
  user: Session["user"];
}) => {
  const where = {
    programId: program.id,
    partnerId: partner.id,
  };

+ try {
    await prisma.$transaction([
      // ... existing transaction operations
    ]);
+ } catch (error) {
+   console.error(`Failed to unban partner ${partner.id}:`, error);
+   throw error;
+ }

  waitUntil(
    (async () => {
      const links = await prisma.link.findMany({
        where,
        select: {
          domain: true,
          key: true,
        },
      });

-     await Promise.allSettled([
+     const results = await Promise.allSettled([
        linkCache.deleteMany(links),
        recordAuditLog({
          // ... existing audit log
        }),
      ]);
+
+     results.forEach((result, index) => {
+       if (result.status === 'rejected') {
+         const operation = index === 0 ? 'cache invalidation' : 'audit logging';
+         console.error(`Failed ${operation} for partner ${partner.id}:`, result.reason);
+       }
+     });
    })(),
  );
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0966e5 and f9612f5.

📒 Files selected for processing (13)
  • apps/web/app/(ee)/api/fraud-events/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (1 hunks)
  • apps/web/lib/actions/partners/ban-partner.ts (2 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts (1 hunks)
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts (1 hunks)
  • apps/web/lib/actions/partners/unban-partner.ts (2 hunks)
  • apps/web/lib/api/partners/ban-partner.ts (1 hunks)
  • apps/web/lib/api/partners/unban-partner.ts (1 hunks)
  • apps/web/lib/partners/constants.ts (0 hunks)
  • apps/web/lib/zod/schemas/fraud-events.ts (1 hunks)
  • apps/web/scripts/fraud-events.ts (1 hunks)
  • apps/web/ui/partners/risk-review-sheet.tsx (1 hunks)
  • packages/prisma/schema/fraud.prisma (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/lib/partners/constants.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/web/lib/actions/partners/ban-partner.ts
  • apps/web/app/(ee)/api/fraud-events/route.ts
  • apps/web/lib/api/partners/ban-partner.ts
  • apps/web/lib/actions/partners/mark-fraud-event-banned.ts
  • apps/web/lib/zod/schemas/fraud-events.ts
  • apps/web/lib/actions/partners/mark-fraud-event-safe.ts
  • packages/prisma/schema/fraud.prisma
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/scripts/fraud-events.ts (1)
apps/web/lib/api/create-id.ts (1)
  • createId (57-66)
apps/web/lib/actions/partners/unban-partner.ts (1)
apps/web/lib/api/partners/unban-partner.ts (1)
  • unbanPartner (10-99)
🔇 Additional comments (8)
apps/web/scripts/fraud-events.ts (1)

1-3: LGTM!

The imports are correctly structured and include the necessary dependencies for the script functionality.

apps/web/lib/actions/partners/unban-partner.ts (2)

3-3: Good refactoring to centralize unbanning logic

Moving the unbanning logic to a dedicated helper function improves code organization and reusability.


32-37: Error handling is intact—no further changes needed

The unbanPartner helper neither swallows nor alters errors from the main transaction (it will reject on failure just as the inline code did), and uses Promise.allSettled for background cache/logging without blocking. Delegation is clean and preserves the previous behavior.

apps/web/lib/api/partners/unban-partner.ts (2)

10-20: Well-typed function signature with appropriate use of Pick types

Good use of TypeScript's Pick utility type to ensure type safety while only requiring the necessary fields from each entity.


26-67: Comprehensive transaction ensures data consistency

The transaction properly handles all aspects of unbanning:

  • Removes link expiration
  • Updates enrollment status from "banned" to "approved"
  • Clears ban metadata
  • Reverts canceled commissions and payouts to "pending"

All operations are atomic, ensuring data consistency.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-table.tsx (2)

224-241: Excellent empty state handling with context-aware messaging

The component provides clear feedback to users with different messages for filtered vs unfiltered empty states, improving the user experience.


307-333: Well-structured conditional action rendering

The menu intelligently shows/hides actions based on the fraud event status:

  • Ban action only for "pending" or "safe" events
  • Mark safe action only for "pending" or "banned" events

This prevents invalid state transitions.

apps/web/ui/partners/risk-review-sheet.tsx (1)

564-586: Clean sheet implementation with proper URL state management

The component properly cleans up the fraudEventId query parameter when the sheet closes, maintaining clean URLs and preventing stale state.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/web/lib/analytics/fraud/is-google-ads-click.ts (1)

40-48: Refine the referer check to reduce false positives

The current implementation checks if "google" appears anywhere in the referer string, which could lead to false positives. For example, referers like https://example.com/google-analytics or https://mygoogleproject.com would incorrectly trigger this condition.

apps/web/ui/partners/risk-review-sheet.tsx (2)

47-49: Handle error state when fetching partner data

The usePartner hook returns an error field, but you only destructure partner and loading. If the fetch fails, the component will render nothing and confuse users.


350-355: Handle undefined commission amounts

The helAmount calculation doesn't explicitly handle the case where fraudEvent.commissions is undefined or null, which could lead to runtime errors.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9612f5 and f74a943.

📒 Files selected for processing (7)
  • apps/web/lib/analytics/fraud/detect-fraud-events.ts (1 hunks)
  • apps/web/lib/analytics/fraud/is-google-ads-click.ts (1 hunks)
  • apps/web/lib/analytics/fraud/record-fraud-if-detected.ts (1 hunks)
  • apps/web/lib/middleware/utils/get-final-url.ts (1 hunks)
  • apps/web/lib/types.ts (2 hunks)
  • apps/web/lib/zod/schemas/fraud-events.ts (1 hunks)
  • apps/web/ui/partners/risk-review-sheet.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/lib/middleware/utils/get-final-url.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/lib/analytics/fraud/detect-fraud-events.ts
  • apps/web/lib/types.ts
  • apps/web/lib/analytics/fraud/record-fraud-if-detected.ts
  • apps/web/lib/zod/schemas/fraud-events.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/ui/partners/risk-review-sheet.tsx
📚 Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/ui/partners/risk-review-sheet.tsx
📚 Learning: in apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined res...
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/ui/partners/risk-review-sheet.tsx
📚 Learning: in react components where arrays are defined with usememo using static array literals (not filtered ...
Learnt from: TWilson023
PR: dubinc/dub#2538
File: apps/web/ui/partners/overview/blocks/conversion-block.tsx:85-85
Timestamp: 2025-06-18T20:31:51.779Z
Learning: In React components where arrays are defined with useMemo using static array literals (not filtered or dynamically populated), the array will always have the same number of elements regardless of the data values. Using .at(-1)! on such arrays is safe because the array structure is constant, even if individual property values within the array elements may be 0 or falsy.

Applied to files:

  • apps/web/ui/partners/risk-review-sheet.tsx
📚 Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/ui/partners/risk-review-sheet.tsx
🪛 Biome (2.1.2)
apps/web/ui/partners/risk-review-sheet.tsx

[error] 470-470: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🔇 Additional comments (6)
apps/web/lib/analytics/fraud/is-google-ads-click.ts (3)

1-12: LGTM - Clean function signature and types

The function signature is well-structured with proper TypeScript types and clear parameter definitions.


13-38: LGTM - Robust Google Ads parameter detection

The URL parameter checking logic correctly identifies Google Ads parameters (gclid, gad_source, gad_campaignid) and safely handles URL parsing with proper null checks.


50-54: LGTM - Appropriate default return

The default return correctly indicates no Google Ads click detected with proper null parameters.

apps/web/ui/partners/risk-review-sheet.tsx (3)

1-42: LGTM - Well-organized imports and type definitions

The component imports are comprehensive and the TypeScript interfaces are properly defined.


119-177: LGTM - Comprehensive partner metrics display

The partner statistics display logic properly handles null values and formats currency amounts correctly, converting from cents to dollars.


282-609: LGTM - Well-structured component hierarchy

The component architecture is well-organized with proper separation of concerns, loading states, error handling, and clean composition patterns.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/web/lib/analytics/fraud/detect-and-record-fraud.ts (2)

19-47: Consider graceful handling of missing program enrollments.

The use of findUniqueOrThrow will cause the function to throw an error if the program enrollment doesn't exist. In a fraud detection context, you might want to handle this scenario more gracefully to avoid breaking downstream processes.

Consider this approach for more resilient error handling:

-  const { partner: partnerData, ...enrollment } =
-    await prisma.programEnrollment.findUniqueOrThrow({
+  const enrollmentData = await prisma.programEnrollment.findUnique({
       where: {
         partnerId_programId: {
           partnerId: partner.id,
           programId: partner.programId,
         },
       },
       // ... rest of select
-    });
+    });
+
+  if (!enrollmentData) {
+    console.log(`Program enrollment not found for partner ${partner.id}`);
+    return;
+  }
+
+  const { partner: partnerData, ...enrollment } = enrollmentData;

89-115: Consider using Prisma's upsert for cleaner code.

The manual upsert pattern works but could be simplified using Prisma's built-in upsert operation.

Consider this approach:

-      let fraudEvent = await tx.fraudEvent.findUnique({
-        where: {
-          partnerId_customerId: {
-            partnerId: partner.id,
-            customerId: customer.id,
-          },
-        },
-      });
-
-      if (!fraudEvent) {
-        fraudEvent = await tx.fraudEvent.create({
-          data: {
+      const fraudEvent = await tx.fraudEvent.upsert({
+        where: {
+          partnerId_customerId: {
+            partnerId: partner.id,
+            customerId: customer.id,
+          },
+        },
+        create: {
             id: createId({ prefix: "fraud_" }),
             selfReferral: fraudTypesFound.has("selfReferral"),
             googleAdsClick: fraudTypesFound.has("googleAdsClick"),
             disposableEmail: fraudTypesFound.has("disposableEmail"),
             programId: partner.programId,
             partnerId: partner.id,
             customerId: customer.id,
             linkId: partner.linkId,
             ...(eventsWithDetails.length > 0 && {
               details: eventsWithDetails as unknown as Prisma.InputJsonValue,
             }),
-          },
-        });
-      }
+        },
+        update: {
+          // Update existing fraud event if needed
+          selfReferral: fraudTypesFound.has("selfReferral"),
+          googleAdsClick: fraudTypesFound.has("googleAdsClick"),
+          disposableEmail: fraudTypesFound.has("disposableEmail"),
+          ...(eventsWithDetails.length > 0 && {
+            details: eventsWithDetails as unknown as Prisma.InputJsonValue,
+          }),
+        },
+      });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a20b60b and b78c960.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts (3 hunks)
  • apps/web/lib/analytics/fraud/detect-and-record-fraud.ts (1 hunks)
  • apps/web/lib/api/conversions/track-lead.ts (3 hunks)
  • apps/web/lib/api/conversions/track-sale.ts (2 hunks)
  • apps/web/lib/integrations/shopify/create-sale.ts (2 hunks)
  • apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (6 hunks)
  • packages/prisma/schema/link.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/prisma/schema/link.prisma
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/lib/integrations/shopify/create-sale.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
  • apps/web/lib/api/conversions/track-sale.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
apps/web/lib/analytics/fraud/detect-and-record-fraud.ts (5)

1-18: LGTM! Well-structured imports and function signature.

The imports are clean and the function signature uses appropriate TypeScript patterns with Pick utility types to enforce minimal required fields. The parameter structure clearly defines the expected data shape.


49-54: LGTM! Appropriate trusted partner bypass.

The early return for trusted partners is correct - fraud detection should be skipped for verified partners. The logging provides good visibility into the decision.


67-80: LGTM! Clean fraud detection logic.

The fraud detection call and empty result handling is well-implemented. The logging provides good visibility and the early return avoids unnecessary processing.


82-86: LGTM! Efficient fraud event processing.

Good use of Set for fraud type lookup and logical filtering for events with detailed information. The functional approach is clean and readable.


129-144: LGTM! Comprehensive error handling.

Excellent error handling that:

  • Gracefully handles expected unique constraint violations from concurrent operations
  • Provides proper logging for debugging and monitoring
  • Uses external logging with alerting for critical errors
  • Follows good practices for transaction error recovery

Comment on lines +56 to +65
// Get partner's IP address from their associated user
let partnerIpAddress: string | null = null;

if (partnerData.users.length > 0) {
const partnerUser = partnerData.users[0]?.user;

if (partnerUser?.ipAddress) {
partnerIpAddress = partnerUser.ipAddress.toString("utf8");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Review IP address extraction logic.

Several considerations for the IP address handling:

  1. Buffer conversion: Using toString("utf8") for IP addresses may not be correct. IP addresses are typically stored as binary data and should be converted differently.

  2. Multiple users: The logic only uses the first user's IP address. Consider if this is the intended behavior when a partner has multiple associated users.

Consider this improved approach:

   if (partnerData.users.length > 0) {
-    const partnerUser = partnerData.users[0]?.user;
+    const partnerUser = partnerData.users[0].user;
 
     if (partnerUser?.ipAddress) {
-      partnerIpAddress = partnerUser.ipAddress.toString("utf8");
+      // Convert Buffer to IP address string properly
+      partnerIpAddress = partnerUser.ipAddress.toString();
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get partner's IP address from their associated user
let partnerIpAddress: string | null = null;
if (partnerData.users.length > 0) {
const partnerUser = partnerData.users[0]?.user;
if (partnerUser?.ipAddress) {
partnerIpAddress = partnerUser.ipAddress.toString("utf8");
}
}
// Get partner's IP address from their associated user
let partnerIpAddress: string | null = null;
if (partnerData.users.length > 0) {
const partnerUser = partnerData.users[0].user;
if (partnerUser?.ipAddress) {
// Convert Buffer to IP address string properly
partnerIpAddress = partnerUser.ipAddress.toString();
}
}
🤖 Prompt for AI Agents
In apps/web/lib/analytics/fraud/detect-and-record-fraud.ts around lines 56 to
65, the IP address extraction uses toString("utf8") which is not appropriate for
binary IP data; instead, convert the buffer to a standard IP string format
(e.g., using a library or appropriate method for IPv4/IPv6). Also, review
whether using only the first user's IP is correct; if multiple users' IPs are
relevant, update the logic to handle all or select the correct one accordingly.

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.

2 participants