Skip to content

Admin flag partner fraud#4089

Open
pepeladeira wants to merge 6 commits into
mainfrom
admin-flag-partner-fraud
Open

Admin flag partner fraud#4089
pepeladeira wants to merge 6 commits into
mainfrom
admin-flag-partner-fraud

Conversation

@pepeladeira

@pepeladeira pepeladeira commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added an admin “Flag Partner” flow in fraud alerts with a dedicated sheet to search partners (by email or ID), collect a required fraud reason, and optionally add a review note before confirming.
    • Fraud alerts now display “Flagged by” details and distinguish admin-originated alerts.
  • Bug Fixes
    • Improved fraud confirmation logic to prevent incorrect/duplicate confirmations and provide clearer conflict handling.
    • Flag submissions now show more accurate success/error messaging and refresh the alert list after completion.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Jun 26, 2026 10:41pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds an admin partner-fraud flagging flow, updates fraud alert data contracts to include alert source information, centralizes fraud confirmation/reporting helpers, and refreshes fraud-alert admin views to show who flagged each alert.

Changes

Fraud alert workflow

Layer / File(s) Summary
Fraud alert contracts
apps/web/prisma/schema/fraud.prisma, apps/web/lib/zod/schemas/admin.ts, apps/web/app/(ee)/api/admin/partners/fraud/route.ts, apps/web/app/(ee)/api/admin/partners/[partnerId]/route.ts
FraudAlert now carries a source field, admin fraud schemas validate the updated alert and flag payload shapes, and the admin read endpoints select and parse the new fields.
Fraud confirmation and reporting helpers
apps/web/lib/api/fraud/constants.ts, apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts, apps/web/lib/api/fraud/report-cross-program-ban-to-network.ts, apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts
Pending partner fraud alerts are confirmed with review metadata, network-ban reporting returns counts, and admin fraud reporting creates events for eligible program enrollments.
Admin fraud routes
apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts, apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts
The fraud-alert PATCH route now delegates to the shared confirmation helper, and the new partner flag route creates an admin-sourced alert, confirms it, and reports affected programs.
Admin fraud UI
apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx, apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/fraud-alert-reporter.tsx, apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/page.tsx, apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx
The fraud admin page adds the flagging sheet flow and reporter-based alert display, and the review sheet uses the shared reporter for current and previous fraud alerts.

Sequence Diagram(s)

sequenceDiagram
  participant AdminFraudPage
  participant FlagPartnerFraudSheet
  participant POSTRoute as POST /api/admin/partners/[partnerId]/flag-fraud
  participant ConfirmPartnerFraudAlerts
  participant ReportAdminFraudToPrograms
  participant Prisma

  AdminFraudPage->>FlagPartnerFraudSheet: open sheet and submit fraud reason
  FlagPartnerFraudSheet->>POSTRoute: POST reason and reviewNote
  POSTRoute->>Prisma: create fraudAlert and fetch enrollments
  POSTRoute->>ConfirmPartnerFraudAlerts: confirm pending partner alerts
  ConfirmPartnerFraudAlerts->>Prisma: update pending fraud alerts
  POSTRoute->>ReportAdminFraudToPrograms: report fraud to active programs
  ReportAdminFraudToPrograms->>Prisma: create fraud events
  POSTRoute-->>FlagPartnerFraudSheet: success + alertedProgramsCount
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • dubinc/dub#3700: Both PRs directly change how FraudRuleType.partnerCrossProgramBan fraud events are emitted and reported.
  • dubinc/dub#3706: Shares the same fraud-alert confirmation route that now delegates to confirmPartnerFraudAlerts.
  • dubinc/dub#4009: Both PRs affect report-cross-program-ban-to-network.ts and its return/count behavior.

Suggested reviewers

  • steven-tey
  • devkiran

Poem

🐇 I hopped through flags and badges bright,
and named each fraud by source tonight.
With a twitch of nose and a careful thump,
I left the admin page in a tidy jump.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly reflects the main change: adding an admin flow to flag partner fraud.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch admin-flag-partner-fraud

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: private package registry requires authentication. Disable ESLint in CodeRabbit settings or use public packages.


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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/admin.ts (1)

97-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive source from the Prisma enum
FRAUD_ALERT_SOURCES duplicates FraudAlertSource from Prisma; switch source to z.enum(FraudAlertSource) so the allowed values stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/zod/schemas/admin.ts` around lines 97 - 108, The admin fraud
schema is hardcoding the allowed source values instead of using the Prisma enum,
so update `adminFraudAlertSchema` to derive `source` from `FraudAlertSource`
rather than `FRAUD_ALERT_SOURCES`. Keep the existing `adminFraudAlertSchema`
shape intact, but replace the `z.enum(...)` input for `source` so the validation
stays aligned with Prisma’s `FraudAlertSource` definition and remove the
duplicated source constant if it is no longer needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/`(ee)/api/admin/fraud-alerts/[id]/route.ts:
- Around line 62-68: The partner-level confirmation path in the fraud alert
route can return success even when the targeted alert is no longer pending,
because confirmPartnerFraudAlerts only checks alerts by fraudAlert.partnerId.
Before calling confirmPartnerFraudAlerts, verify the specific fraudAlert
referenced by the route parameter is still pending; if it has already been
reviewed, return the expected 409 instead of proceeding. Update the handler in
the route’s main function so the check happens against the targeted alert
record, not just the partner-wide batch.

In `@apps/web/app/`(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts:
- Around line 52-74: The flagging flow in the admin fraud route is not
idempotent after confirmation succeeds but reportAdminFraudToPrograms fails,
causing retries to create duplicate fraud alerts. Update the flow around
confirmPartnerFraudAlerts and reportAdminFraudToPrograms so a previously
committed admin fraud alert is reused or deduplicated for the same partner,
either by checking for an existing admin fraud alert before
prisma.fraudAlert.create, adding an idempotency key tied to the committed alert,
or moving the cross-program reporting into a retryable outbox/background step
keyed by that alert.

In `@apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts`:
- Around line 70-82: The confirmation flow in confirm-partner-fraud-alerts
currently awaits reportCrossProgramBanToNetwork via Promise.all, so a single
rejection can throw after the alerts have already been confirmed. Update the
helper to avoid failing the request once confirmation is committed:
capture/report network failures separately from the confirmation path, and keep
the success response based on the confirmation result even if some reporting
calls fail. Use the confirm-partner-fraud-alerts helper and
reportCrossProgramBanToNetwork call as the main locations to adjust.
- Around line 50-61: The confirmation result in confirmPartnerFraudAlerts is
using the pre-update pending list instead of the actual
prisma.fraudAlert.updateMany outcome. Capture the updateMany return value and
use its count as confirmedCount, then gate any reporting/side effects on that
count so confirmPartnerFraudAlerts only reports rows actually updated by this
call.

In `@apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts`:
- Around line 15-22: The admin fraud targeting query in
report-admin-fraud-to-programs should exclude the network program so its own
enrollment does not get counted as an alert target and inflate
alertedProgramsCount. Update the prisma.programEnrollment.findMany filter inside
report-admin-fraud-to-programs to add a NETWORK_PROGRAM_ID exclusion alongside
the existing partnerId, status, and riskMonitoringDisabledAt conditions, and
make sure the later aggregation logic that builds the affected program
list/count uses the filtered results consistently.

---

Nitpick comments:
In `@apps/web/lib/zod/schemas/admin.ts`:
- Around line 97-108: The admin fraud schema is hardcoding the allowed source
values instead of using the Prisma enum, so update `adminFraudAlertSchema` to
derive `source` from `FraudAlertSource` rather than `FRAUD_ALERT_SOURCES`. Keep
the existing `adminFraudAlertSchema` shape intact, but replace the `z.enum(...)`
input for `source` so the validation stays aligned with Prisma’s
`FraudAlertSource` definition and remove the duplicated source constant if it is
no longer needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 866db5fb-0669-42bc-9c14-17753918bbc6

📥 Commits

Reviewing files that changed from the base of the PR and between 0d95d4b and d3e0a4f.

📒 Files selected for processing (14)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/fraud-alert-reporter.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/page.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx
  • apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/route.ts
  • apps/web/app/(ee)/api/admin/partners/fraud/route.ts
  • apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts
  • apps/web/lib/api/fraud/constants.ts
  • apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts
  • apps/web/lib/api/fraud/report-cross-program-ban-to-network.ts
  • apps/web/lib/zod/schemas/admin.ts
  • apps/web/prisma/schema/fraud.prisma

Comment thread apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts
Comment thread apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts Outdated
Comment thread apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts Outdated
Comment thread apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts Outdated
Comment thread apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts`:
- Around line 74-79: The reporting re-fetch in confirm-partner-fraud-alerts
should only include rows updated by this specific call, not any later concurrent
confirmations. Reuse the exact reviewedAt value written in the
prisma.fraudAlert.updateMany call and add it to the fraudAlert.findMany filter
alongside status and reviewedById so only rows from this batch are re-fetched
for reporting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8db6111b-ab64-413d-8b2c-38c6d8a3100a

📥 Commits

Reviewing files that changed from the base of the PR and between d3e0a4f and ac49264.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts
  • apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts
  • apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts
  • apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts

Comment thread apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts (1)

23-27: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope skipped reporting to the admin alert batch.

The admin flag route calls this helper with skipCrossProgramReporting: true, but this query selects all pending alerts for the partner. If a program-sourced pending alert exists concurrently, it gets confirmed and Line 68 returns before its cross-program reporting runs.

Proposed fix
 export async function confirmPartnerFraudAlerts({
   partnerId,
   reviewedById,
   reviewNote,
   skipCrossProgramReporting = false,
+  source,
 }: {
   partnerId: string;
   reviewedById: string;
   reviewNote?: string;
   skipCrossProgramReporting?: boolean;
+  source?: "program" | "admin";
 }) {
@@
     where: {
       partnerId,
       status: "pending",
+      ...(source ? { source } : {}),
     },

And pass source: "admin" from the admin flag route when using skipCrossProgramReporting: true.

Also applies to: 68-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts` around lines 23 - 27,
The pending alert lookup in confirmPartnerFraudAlerts is too broad and can
consume program-sourced alerts when the admin path sets
skipCrossProgramReporting, so narrow the prisma.fraudAlert.findMany filter to
only the admin-sourced pending alert(s) for that flow. Then update the admin
flag route to pass source: "admin" whenever skipCrossProgramReporting: true is
used, and make sure the early return around the confirmation/reporting branch
still preserves cross-program reporting for non-admin alerts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts`:
- Around line 23-27: The pending alert lookup in confirmPartnerFraudAlerts is
too broad and can consume program-sourced alerts when the admin path sets
skipCrossProgramReporting, so narrow the prisma.fraudAlert.findMany filter to
only the admin-sourced pending alert(s) for that flow. Then update the admin
flag route to pass source: "admin" whenever skipCrossProgramReporting: true is
used, and make sure the early return around the confirmation/reporting branch
still preserves cross-program reporting for non-admin alerts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4fd6a07-fdcb-4ab2-b311-4b335816cf8c

📥 Commits

Reviewing files that changed from the base of the PR and between ac49264 and 3e8df38.

📒 Files selected for processing (1)
  • apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts

@pepeladeira

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review, any breaking changes?

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Changed files vs main =="
git diff --name-only origin/main...HEAD

echo
echo "== Diff summary =="
git diff --stat origin/main...HEAD

echo
echo "== Outline: confirm-partner-fraud-alerts.ts =="
ast-grep outline 'apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts' --view expanded || true

echo
echo "== confirm-partner-fraud-alerts.ts =="
sed -n '1,260p' 'apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts'

echo
echo "== flag-fraud route =="
sed -n '1,260p' 'apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts'

echo
echo "== fraud alert PATCH route =="
sed -n '1,260p' 'apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts'

echo
echo "== admin zod schemas =="
sed -n '1,260p' 'apps/web/lib/zod/schemas/admin.ts'

echo
echo "== admin partners route =="
sed -n '1,260p' 'apps/web/app/(ee)/api/admin/partners/[partnerId]/route.ts'

echo
echo "== fraud alerts list route =="
sed -n '1,220p' 'apps/web/app/(ee)/api/admin/partners/fraud/route.ts'

echo
echo "== fraud prisma schema =="
sed -n '1,220p' 'apps/web/prisma/schema/fraud.prisma'

echo
echo "== Search for confirmPartnerFraudAlerts call sites =="
rg -n -C2 '\bconfirmPartnerFraudAlerts\s*\(' apps/web

echo
echo "== Search for adminFraudAlertSchema call sites =="
rg -n -C2 '\badminFraudAlertSchema\b' apps/web

echo
echo "== Search for direct assumptions on fraudAlert.source =="
rg -n -C2 '\bsource\b' apps/web/app/(ee) apps/web/lib | sed -n '1,220p'

Length of output: 32671


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== page.tsx =="
sed -n '1,260p' 'apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/page.tsx'

echo
echo "== review-fraud-alert-sheet.tsx =="
sed -n '1,320p' 'apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx'

echo
echo "== fraud-alert-reporter.tsx =="
sed -n '1,220p' 'apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/fraud-alert-reporter.tsx'

echo
echo "== report-admin-fraud-to-programs.ts =="
sed -n '1,260p' 'apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts'

echo
echo "== report-cross-program-ban-to-network.ts =="
sed -n '1,260p' 'apps/web/lib/api/fraud/report-cross-program-ban-to-network.ts'

echo
echo "== constants.ts =="
sed -n '1,220p' 'apps/web/lib/api/fraud/constants.ts'

echo
echo "== flag-partner-fraud-sheet.tsx =="
sed -n '1,340p' 'apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx'

echo
echo "== Check for Prisma migrations touching FraudAlertSource or FraudAlert.source =="
fd . 'apps/web/prisma' | sed -n '1,220p'
echo
rg -n -C2 'FraudAlertSource|source\s+FraudAlertSource|ALTER TABLE.*FraudAlert|CREATE TYPE.*FraudAlertSource' 'apps/web/prisma' || true

echo
echo "== Search for callers / consumers of /api/admin/partners/[partnerId] fraudAlerts payload =="
rg -n -C2 'fraudAlerts' 'apps/web/app' 'apps/web/lib' | sed -n '1,260p'

echo
echo "== Search for strict assumptions about FraudAlert fields/status/source in UI =="
rg -n -C2 'source|confirmed|dismissed|pending|Flagged by|Program' \
  'apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud' \
  'apps/web/app/(ee)/api/admin/partners' \
  'apps/web/app/(ee)/api/admin/fraud-alerts' | sed -n '1,260p'

Length of output: 72746



@pepeladeira I did a pass on the touched API/UI paths.

Short answer: no obvious consumer-facing breaking API change inside this PR, since the updated routes, schemas, and admin UI were changed consistently together.

Two things I’d still call out:

  1. Deployment-sensitive break risk

    • apps/web/prisma/schema/fraud.prisma adds a new required FraudAlert.source enum field (Line 92, Line 103).
    • The server now selects and validates that field in:
      • apps/web/app/(ee)/api/admin/partners/fraud/route.ts (Line 45, Line 76)
      • apps/web/app/(ee)/api/admin/partners/[partnerId]/route.ts (Line 114)
      • apps/web/lib/zod/schemas/admin.ts (adminFraudAlertSchema, Line 108)
    • So if the DB migration / Prisma client update is not rolled out with this code, this can fail at runtime.
  2. Behavior change worth validating

    • apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts now confirms alerts by partnerId (optionally by source), not by alert id.
    • That means PATCH /api/admin/fraud-alerts/[id] confirmation now effectively confirms all pending alerts for that partner, not just the selected alert.
    • If that’s intended, great — but it is the main semantic change I’d classify as potentially “breaking” from an expectations/workflow standpoint.

So my read is:

  • No clear contract break for the updated admin UI/API consumers.
  • Yes, one rollout risk (schema migration).
  • Yes, one meaningful behavior change (bulk confirm by partner).

I’ll kick off the full re-review now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts (1)

22-30: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

This turns an id-scoped PATCH into a partner-wide review.

confirmPartnerFraudAlerts only scopes by partnerId plus optional source (see apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts, Lines 25-30), so this endpoint can confirm unrelated pending alerts for the same partner. It can also return 200 when the targeted alert lost the race but another pending alert was still available to batch-confirm. Keep this route id-scoped, or make the batch behavior explicit in the route/UI contract.

Also applies to: 69-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(ee)/api/admin/fraud-alerts/[id]/route.ts around lines 22 - 30,
The PATCH handler in the fraud alert route is unintentionally batching by
partner instead of staying id-scoped, because it passes the alert’s partnerId
into confirmPartnerFraudAlerts and can confirm other pending alerts for that
partner. Update the route logic around fraudAlert lookup and
confirmPartnerFraudAlerts so the endpoint only acts on the requested alert id,
or explicitly change the route/UI contract to document partner-wide batch
confirmation; if keeping batch behavior, make the success path and naming
reflect that it may confirm a different pending alert for the same partner.
apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx (1)

126-143: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The confirmation prompt no longer matches what this action does.

PATCH /api/admin/fraud-alerts/[id] now confirms all pending alerts for the same partner (and source), but the dialog still asks about confirming “this fraud alert”. That can bulk-confirm additional alerts without warning. Either restore single-alert confirmation on the server or update the copy/UX to disclose the wider scope before submit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx
around lines 126 - 143, The confirmation flow in handleReview no longer matches
the PATCH /api/admin/fraud-alerts/[id] behavior, since it can confirm multiple
pending alerts for the same partner/source while the dialog still says “this
fraud alert.” Update the copy/UX in review-fraud-alert-sheet.tsx to clearly
disclose the broader scope before submit, or change the API/server behavior back
to single-alert confirmation. Make sure the user sees an accurate warning in the
handleReview confirmation prompt before calling fetch.
♻️ Duplicate comments (1)
apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts (1)

52-97: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The retry path still creates duplicate admin alerts.

If reportAdminFraudToPrograms throws after Line 72 confirms the alert, a retry will miss the existing row at Lines 52-59 because it is already confirmed, then create a second admin alert at Lines 61-69 for the same action. The read-then-create window is also racy under concurrent submissions. This needs an idempotent key/reuse strategy that survives post-confirmation failures, not just pending-row dedupe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts around
lines 52 - 97, The admin fraud route still creates duplicate alerts on retries
because the lookup in the partner fraud handler only reuses a pending admin row,
then creates a new one if the prior attempt was already confirmed; update the
logic in the flag-fraud route to make alert creation idempotent across retries
and concurrent submissions. Use the existing fraud alert lookup/create flow
around prisma.fraudAlert.findFirst/create and confirmPartnerFraudAlerts to reuse
any prior admin alert for the same partner/action (for example via a stable
idempotency key or broader reuse criteria), so a failed
reportAdminFraudToPrograms retry does not insert a second admin fraudAlert.
🧹 Nitpick comments (2)
apps/web/prisma/schema/fraud.prisma (1)

92-103: 🩺 Stability & Availability | 🔵 Trivial

Plan an atomic rollout for FraudAlert.source.

This field is required here, and the updated admin API/schema now read it immediately. If app code rolls out before the migration and Prisma client regen, the first source select will fail at runtime. Deploy them together or gate the read path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/prisma/schema/fraud.prisma` around lines 92 - 103, The new
FraudAlert.source field is now read by the admin API/schema, so app code must
not ship ahead of the database migration and Prisma client regeneration.
Coordinate the rollout around FraudAlert and FraudAlertSource so the schema
change, migration, and generated client are deployed together, or gate the read
path until source is guaranteed to exist in all records and the client is
updated.
apps/web/lib/zod/schemas/admin.ts (1)

97-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid duplicating the persisted fraud-source enum.

FRAUD_ALERT_SOURCES now mirrors the Prisma FraudAlertSource contract, so a future enum change can make this parser reject valid rows even though the database and Prisma client are already updated. Zod 4 can validate the generated enum directly and keep this boundary single-sourced. Import FraudAlertSource alongside FraudAlertStatus.

♻️ Proposed refactor
-export const FRAUD_ALERT_SOURCES = ["program", "admin"] as const;
-
 export const adminFlagFraudSchema = z.object({
   reason: z.string().trim().min(1).max(MAX_FRAUD_REASON_LENGTH),
   reviewNote: z.string().trim().max(MAX_FRAUD_REASON_LENGTH).optional(),
 });
 
 export const adminFraudAlertSchema = z.object({
   id: z.string(),
   reason: z.string(),
   status: z.enum(FraudAlertStatus),
-  source: z.enum(FRAUD_ALERT_SOURCES),
+  source: z.enum(FraudAlertSource),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/zod/schemas/admin.ts` around lines 97 - 108,
`adminFraudAlertSchema` is duplicating the persisted fraud-source enum via
`FRAUD_ALERT_SOURCES`, which can drift from Prisma and reject valid rows.
Replace the local source array in `apps/web/lib/zod/schemas/admin.ts` with the
generated `FraudAlertSource` enum imported alongside `FraudAlertStatus`, and
update the `source` field in `adminFraudAlertSchema` to validate against that
single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/app/`(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx:
- Around line 100-115: The flag flow in flag-partner-fraud-sheet.tsx is showing
a success toast before the follow-up refresh via onFlagged() completes, so a
revalidation failure can trigger an unnecessary error toast after success.
Update the handler around the success toast and await onFlagged() so the refresh
is handled as a separate best-effort step, or move the toast.success call until
after onFlagged() succeeds; keep the error handling in the same flag submission
path.

---

Outside diff comments:
In
`@apps/web/app/`(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx:
- Around line 126-143: The confirmation flow in handleReview no longer matches
the PATCH /api/admin/fraud-alerts/[id] behavior, since it can confirm multiple
pending alerts for the same partner/source while the dialog still says “this
fraud alert.” Update the copy/UX in review-fraud-alert-sheet.tsx to clearly
disclose the broader scope before submit, or change the API/server behavior back
to single-alert confirmation. Make sure the user sees an accurate warning in the
handleReview confirmation prompt before calling fetch.

In `@apps/web/app/`(ee)/api/admin/fraud-alerts/[id]/route.ts:
- Around line 22-30: The PATCH handler in the fraud alert route is
unintentionally batching by partner instead of staying id-scoped, because it
passes the alert’s partnerId into confirmPartnerFraudAlerts and can confirm
other pending alerts for that partner. Update the route logic around fraudAlert
lookup and confirmPartnerFraudAlerts so the endpoint only acts on the requested
alert id, or explicitly change the route/UI contract to document partner-wide
batch confirmation; if keeping batch behavior, make the success path and naming
reflect that it may confirm a different pending alert for the same partner.

---

Duplicate comments:
In `@apps/web/app/`(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts:
- Around line 52-97: The admin fraud route still creates duplicate alerts on
retries because the lookup in the partner fraud handler only reuses a pending
admin row, then creates a new one if the prior attempt was already confirmed;
update the logic in the flag-fraud route to make alert creation idempotent
across retries and concurrent submissions. Use the existing fraud alert
lookup/create flow around prisma.fraudAlert.findFirst/create and
confirmPartnerFraudAlerts to reuse any prior admin alert for the same
partner/action (for example via a stable idempotency key or broader reuse
criteria), so a failed reportAdminFraudToPrograms retry does not insert a second
admin fraudAlert.

---

Nitpick comments:
In `@apps/web/lib/zod/schemas/admin.ts`:
- Around line 97-108: `adminFraudAlertSchema` is duplicating the persisted
fraud-source enum via `FRAUD_ALERT_SOURCES`, which can drift from Prisma and
reject valid rows. Replace the local source array in
`apps/web/lib/zod/schemas/admin.ts` with the generated `FraudAlertSource` enum
imported alongside `FraudAlertStatus`, and update the `source` field in
`adminFraudAlertSchema` to validate against that single source of truth.

In `@apps/web/prisma/schema/fraud.prisma`:
- Around line 92-103: The new FraudAlert.source field is now read by the admin
API/schema, so app code must not ship ahead of the database migration and Prisma
client regeneration. Coordinate the rollout around FraudAlert and
FraudAlertSource so the schema change, migration, and generated client are
deployed together, or gate the read path until source is guaranteed to exist in
all records and the client is updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1daae292-7ac0-4556-8c64-701cd4e104aa

📥 Commits

Reviewing files that changed from the base of the PR and between 0d95d4b and 9d23730.

📒 Files selected for processing (14)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/fraud-alert-reporter.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/page.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/review-fraud-alert-sheet.tsx
  • apps/web/app/(ee)/api/admin/fraud-alerts/[id]/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/flag-fraud/route.ts
  • apps/web/app/(ee)/api/admin/partners/[partnerId]/route.ts
  • apps/web/app/(ee)/api/admin/partners/fraud/route.ts
  • apps/web/lib/api/fraud/confirm-partner-fraud-alerts.ts
  • apps/web/lib/api/fraud/constants.ts
  • apps/web/lib/api/fraud/report-admin-fraud-to-programs.ts
  • apps/web/lib/api/fraud/report-cross-program-ban-to-network.ts
  • apps/web/lib/zod/schemas/admin.ts
  • apps/web/prisma/schema/fraud.prisma

Comment on lines +100 to +115
toast.success(
alertedProgramsCount > 0
? `Fraud confirmed. ${alertedProgramsCount} program${alertedProgramsCount === 1 ? "" : "s"} alerted.`
: "Fraud confirmed. No other enrolled programs to alert.",
);

setSearch("");
setDebouncedSearch("");
setReason("");
setReviewNote("");
setIsOpen(false);
await onFlagged();
} catch (error) {
toast.error(
error instanceof Error ? error.message : "Something went wrong",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t show a failure toast after the flag already succeeded.

toast.success(...) runs before await onFlagged(), so a refresh/revalidation failure here will surface as a second error toast for an action that already completed. Split the follow-up refresh into its own best-effort path, or move the success toast after it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(ee)/admin.dub.co/(dashboard)/partners/fraud/flag-partner-fraud-sheet.tsx
around lines 100 - 115, The flag flow in flag-partner-fraud-sheet.tsx is showing
a success toast before the follow-up refresh via onFlagged() completes, so a
revalidation failure can trigger an unnecessary error toast after success.
Update the handler around the success toast and await onFlagged() so the refresh
is handled as a separate best-effort step, or move the toast.success call until
after onFlagged() succeeds; keep the error handling in the same flag submission
path.

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.

1 participant