-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Resolve fraud events synchronously in banPartner #3174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughMoved auto-resolution of pending fraud events out of the cron ban-processing route into partner ban actions; added logging of resolved counts; updated modal onConfirm callbacks to be asynchronous and awaited, and adjusted where cache invalidation (mutatePrefix) is invoked. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Client (UI)
participant Actions as Server Actions
participant Cron as Server Cron Route
participant FraudAPI as Fraud API
participant DB as Database
participant Audit as Audit / Queue
Note over UI,Actions: New ban flow (single / bulk)
UI->>Actions: request ban (single/bulk)
Actions->>DB: update programEnrollment(s) set banned
Actions->>FraudAPI: resolveFraudEvents(where: partner/program or enrollmentIds)
FraudAPI->>DB: updateMany fraud_events (set resolved)
FraudAPI-->>Actions: { count } resolved
Actions->>Audit: create audit log / queue jobs
Actions-->>UI: respond success
Note over Cron: Cron route now only creates cross-program fraud events
Cron->>DB: query bans
Cron->>FraudAPI: createFraudEvents (cross-program)
FraudAPI->>DB: insert fraud_events
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touchesβ Failed checks (1 warning)
β Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
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/actions/partners/ban-partner.ts (1)
5-89: Beware partialβfailure window betweenupdateandwaitUntilThe new
resolveFraudEventscall is correctly wired and the sharedcommonWhereis a nice reuse. However, its placement introduces a new partialβfailure scenario:
prisma.programEnrollment.updatesucceeds and the partner is banned.resolveFraudEventsthen throws (DB blip, bug, etc.).- The action rejects before reaching
waitUntil, so:
- the caller sees an error even though the partner is already banned,
- no audit log is recorded,
- the cron
/api/cron/partners/ban/processjob is never enqueued,- pending fraud events for this partner/program are left unresolved,
- a subsequent attempt to ban the partner hits the βalready bannedβ guard and never retries resolution.
If you want banβside effects (logging, cron job, and fraudβevent resolution) to be bestβeffort rather than allβorβnothing, consider one of:
- Wrapping
resolveFraudEventsin a try/catch that logs but does not abort the ban flow:try { await resolveFraudEvents({ where: commonWhere, userId: user.id, resolutionReason: "Resolved automatically because the partner was banned.", }); } catch (error) { // TODO: inject your logging util instead of console console.error("Failed to resolve fraud events after ban", error, { partnerId, programId, }); }
- Or, if youβre comfortable with asynchronous resolution, moving
resolveFraudEventsinto thewaitUntil(Promise.allSettled([...]))block alongside the audit log and enqueue logic so its failures donβt affect the main action result.If the current βfail the action if resolution failsβ behavior is intentional, itβd be good to document that tradeβoff, since it means a single transient failure can leave a banned partner without the usual postβban processing.
π§Ή Nitpick comments (2)
apps/web/lib/api/fraud/resolve-fraud-events.ts (1)
56-71: Logging addition afterupdateManylooks goodUsing the
countfromupdateManyand logging per group adds useful observability without changing core behavior. Even if a race leavescount === 0, the log is still accurate, just something to be aware of. You may optionally include the fraudtypeorresolutionReasonin the log if you want richer debugging context, but itβs not required.apps/web/lib/actions/partners/bulk-ban-partners.ts (1)
4-79: Bulk fraudβevent resolution wiring looks correctHooking
resolveFraudEventsinto the bulk ban flow after theupdateManyonprogramEnrollmentensures pending fraud events tied to those enrollments are resolved as part of the operation, which matches the intent of this PR. The relation filter onprogramEnrollment.idis an appropriate way to scope the events.If you want to trim a tiny bit of duplication, you could hoist the IDs into a local array and reuse it in both
updateManyandresolveFraudEvents:const programEnrollmentIds = programEnrollments.map(({ id }) => id); await prisma.programEnrollment.updateMany({ where: { id: { in: programEnrollmentIds } }, // ... }); await resolveFraudEvents({ where: { programEnrollment: { id: { in: programEnrollmentIds }, }, }, userId: user.id, resolutionReason: "Resolved automatically because the partner was banned.", });Purely a nit; current code is functionally fine.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (4)
apps/web/app/(ee)/api/cron/partners/ban/process/route.ts(1 hunks)apps/web/lib/actions/partners/ban-partner.ts(3 hunks)apps/web/lib/actions/partners/bulk-ban-partners.ts(2 hunks)apps/web/lib/api/fraud/resolve-fraud-events.ts(2 hunks)
π§° Additional context used
π§ Learnings (6)
π Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
π Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/lib/actions/partners/bulk-ban-partners.tsapps/web/app/(ee)/api/cron/partners/ban/process/route.tsapps/web/lib/actions/partners/ban-partner.tsapps/web/lib/api/fraud/resolve-fraud-events.ts
π Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/lib/actions/partners/bulk-ban-partners.tsapps/web/app/(ee)/api/cron/partners/ban/process/route.tsapps/web/lib/actions/partners/ban-partner.ts
π Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/lib/actions/partners/bulk-ban-partners.tsapps/web/app/(ee)/api/cron/partners/ban/process/route.tsapps/web/lib/actions/partners/ban-partner.ts
π Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.
Applied to files:
apps/web/app/(ee)/api/cron/partners/ban/process/route.tsapps/web/lib/actions/partners/ban-partner.ts
π Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/lib/actions/partners/delete-reward.ts:33-41
Timestamp: 2025-08-14T05:17:51.825Z
Learning: In the partner groups system, a rewardId can only belong to one group, establishing a one-to-one relationship between rewards and groups. This means using Prisma's `update` method (rather than `updateMany`) is appropriate when updating groups by rewardId.
Applied to files:
apps/web/lib/api/fraud/resolve-fraud-events.ts
𧬠Code graph analysis (3)
apps/web/app/(ee)/api/cron/partners/ban/process/route.ts (1)
apps/web/lib/api/fraud/create-fraud-events.ts (1)
createFraudEvents(13-39)
apps/web/lib/actions/partners/ban-partner.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/fraud/resolve-fraud-events.ts (1)
resolveFraudEvents(6-75)
apps/web/lib/api/fraud/resolve-fraud-events.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
β° 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 (1)
apps/web/app/(ee)/api/cron/partners/ban/process/route.ts (1)
158-166: Crossβprogram fraud event creation remains consistent with rule designThe simplified block that only calls
createFraudEventsforpartnerCrossProgramBanon other approved programs is sound and matches the intent of handling crossβprogram risk separately from currentβprogram resolution. This aligns with the earlier decision thatpartnerCrossProgramBanis a partnerβscoped rule handled via dedicated flows rather than perβconversion evaluation. Based on learnings, this looks correct.
|
@coderabbitai full review |
β Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
π§Ή Nitpick comments (1)
apps/web/ui/modals/ban-partner-modal.tsx (1)
53-62: Consider adding error handling around the onConfirm callback.If the optional
onConfirmcallback throws an exception (e.g., if cache invalidation viamutatePrefixfails), the error may not be caught, potentially leaving the user without proper feedback even though the server-side ban succeeded.Apply this diff to add error handling:
const { executeAsync, isPending } = useAction(banPartnerAction, { onSuccess: async () => { - await onConfirm?.(); + try { + await onConfirm?.(); + } catch (error) { + console.error("Failed to invalidate cache after ban:", error); + // Continue with success flow since server action succeeded + } toast.success("Partner banned successfully!"); setShowBanPartnerModal(false); }, onError({ error }) { toast.error(error.serverError); }, });
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (4)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsx(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx(2 hunks)apps/web/ui/modals/ban-partner-modal.tsx(3 hunks)apps/web/ui/modals/bulk-ban-partners-modal.tsx(3 hunks)
π§° Additional context used
π§ Learnings (5)
π Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
π Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsxapps/web/ui/modals/ban-partner-modal.tsxapps/web/ui/modals/bulk-ban-partners-modal.tsx
π Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsx
π Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsx
β° 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/ui/modals/ban-partner-modal.tsx (1)
35-35: LGTM! Async onConfirm pattern improves flow control.The change from synchronous to asynchronous
onConfirmproperly sequences post-confirmation side effects before UI updates (toast and modal close). The pattern ensures cache invalidation completes before the user sees success feedback.Also applies to: 53-58, 191-191
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-event-groups-table.tsx (2)
97-100: LGTM! Cache invalidation correctly sequenced with UI updates.The async onConfirm callbacks properly trigger cache invalidation via
mutatePrefix, ensuring the fraud events table refreshes after a successful ban. The row selection reset (line 98) executes synchronously for immediate user feedback, followed by the awaited mutation.Also applies to: 379-381
377-382: The partner field is always non-null for fraud events. TheFraudEventmodel in the Prisma schema definespartnerId: Stringas a required (non-nullable) field, and all fraud event creation paths explicitly setpartnerIdfrom a partner object. The schema validation inget-grouped-fraud-events.tswill always succeed because the database constraint ensures every fraud event has an associated partner. The code at lines 377-382 is safe and does not require a null check.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)
173-175: LGTM! Consistent cache invalidation pattern.The async onConfirm callbacks correctly trigger cache invalidation for the partners table after successful bans. The pattern is consistent with the changes in fraud-event-groups-table.tsx and properly awaits the mutation before allowing UI updates to proceed.
Note: The same error handling concern mentioned for ban-partner-modal.tsx applies hereβif
mutatePrefixthrows, it may interrupt the success flow.Also applies to: 639-641
apps/web/ui/modals/bulk-ban-partners-modal.tsx (1)
30-30: LGTM! Async onConfirm pattern matches the single ban modal.The changes mirror those in ban-partner-modal.tsx, providing consistent behavior across both single and bulk ban operations. The async
onConfirmcallback is properly awaited before showing success feedback and closing the modal.Note: The same error handling concern mentioned for ban-partner-modal.tsx applies here as well.
Also applies to: 56-62, 225-225
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
π§Ή Nitpick comments (1)
apps/web/ui/partners/fraud-risks/fraud-review-sheet.tsx (1)
51-56: AsynconConfirmforuseBanPartnerModalmatches expected modal flowSwitching
onConfirmtoasync () => { onNext?.(); }is a good fit ifuseBanPartnerModalnow awaitsonConfirm; it preserves behavior while satisfying the async contract. As a minor consistency tweak, ifuseResolveFraudEventsModalalso treatsonConfirmas async and awaits it, you might consider wrapping that callback inasync () => { onNext?.(); }as well, but itβs not strictly necessary if the type allowsvoid | Promise<void>.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (1)
apps/web/ui/partners/fraud-risks/fraud-review-sheet.tsx(1 hunks)
π§° Additional context used
π§ Learnings (3)
π Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
π Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/ui/partners/fraud-risks/fraud-review-sheet.tsx
π Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/ui/partners/fraud-risks/fraud-review-sheet.tsx
β° 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
Summary by CodeRabbit
Bug Fixes
Improvements
βοΈ Tip: You can customize this high-level summary in your review settings.