-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Tolt importer #2600
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
Tolt importer #2600
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
""" WalkthroughThis update introduces comprehensive support for importing affiliate program data from Tolt. It adds backend modules for Tolt API integration, data import, credential management, partner cleanup, and Stripe customer synchronization. The UI is extended with a new modal and import option for Tolt. Supporting types, schemas, and email template updates are included for end-to-end Tolt import functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebApp
participant ImportToltModal
participant ServerActions
participant ToltApi
participant DB
participant Queue
User->>WebApp: Clicks "Import from Tolt"
WebApp->>ImportToltModal: Show modal
ImportToltModal->>User: Collect API key & Program ID
User->>ImportToltModal: Submit credentials
ImportToltModal->>ServerActions: setToltTokenAction
ServerActions->>ToltApi: Fetch program details
ToltApi->>ServerActions: Return program data
ServerActions->>DB: Store credentials
ServerActions->>ImportToltModal: Return program info
ImportToltModal->>User: Display program info
User->>ImportToltModal: Start import
ImportToltModal->>ServerActions: startToltImportAction
ServerActions->>Queue: Queue "import-affiliates" action
Note right of Queue: Import pipeline begins (affiliates, links, referrals, etc.)
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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: 9
🧹 Nitpick comments (8)
apps/web/lib/api/errors.ts (1)
125-125
: Consider the impact on error log clarity.Removing the "API error occurred" prefix reduces context in error logs, which may make debugging more challenging. Developers won't immediately know that logged errors originated from API handlers.
If cleaner logs are desired, consider using a structured logging approach instead of removing contextual information entirely.
apps/web/lib/actions/partners/start-tolt-import.ts (1)
33-39
: Consider validating credential freshness.The code checks if credentials exist but doesn't verify they're still valid. Tolt API tokens might expire or become invalid, which could cause the queued import to fail.
Consider adding a quick validation call to the Tolt API before queuing the import task.
apps/web/app/(ee)/api/cron/import/tolt/route.ts (1)
31-47
: Consider adding exhaustiveness check for the switch statement.While the schema validation should prevent invalid actions, consider adding a TypeScript exhaustiveness check for better type safety:
switch (action) { case "import-affiliates": await importAffiliates(payload); break; case "import-links": await importLinks(payload); break; case "import-referrals": await importReferrals(payload); break; case "import-commissions": await importCommissions(payload); break; case "update-stripe-customers": await updateStripeCustomers(payload); break; + default: + // Exhaustiveness check + const _exhaustive: never = action; + throw new Error(`Unknown action: ${_exhaustive}`); }apps/web/lib/tolt/update-stripe-customers.ts (1)
118-121
: Use appropriate log level for expected scenarios.Finding no Stripe customer might be expected behavior rather than an error.
if (stripeCustomers.data.length === 0) { - console.error(`Stripe search returned no customer for ${customer.email}`); + console.log(`No Stripe customer found for ${customer.email}`); return null; }apps/web/lib/tolt/import-commissions.ts (3)
97-97
: Use optional chaining for safer property access.The static analysis correctly identifies that this can be simplified with optional chaining.
- if (workspaceUser && workspaceUser.user.email) { + if (workspaceUser?.user.email) {
144-165
: Remove commented dead code.This large block of commented code should be removed to improve code cleanliness and maintainability.
- // here, we also check for commissions that have already been recorded on Dub - // e.g. during the transition period - // since we don't have the Stripe invoiceId from Rewardful, we use the referral's Stripe customer ID - // and check for commissions that were created with the same amount and within a +-1 hour window - // const chargedAt = new Date(sale.created_at); - // const trackedCommission = await prisma.commission.findFirst({ - // where: { - // programId, - // type: "sale", - // customer: { - // stripeCustomerId: sale.referral.stripe_customer_id, - // }, - // amount: Number(sale.amount), - // createdAt: { - // gte: new Date(chargedAt.getTime() - 60 * 60 * 1000), // 1 hour before - // lte: new Date(chargedAt.getTime() + 60 * 60 * 1000), // 1 hour after - // }, - // }, - // }); - - // if (trackedCommission) { - // console.log( - // `Commission ${trackedCommission.id} was already recorded on Dub, skipping...`, - // ); - // return; - // }
230-230
: Address TODO comments about currency handling.The hardcoded "usd" currency should be resolved. Consider adding currency to the Tolt schema or fetching it from the program/workspace configuration.
Would you like me to help implement proper currency handling by either:
- Adding currency field to the Tolt schemas
- Fetching currency from program/workspace settings
- Creating a currency mapping from Tolt program data
Also applies to: 245-245
apps/web/lib/tolt/api.ts (1)
16-16
: Consider increasing PAGE_LIMIT for better batch processing performance.A page limit of 10 items seems quite small for batch import operations. This could lead to excessive API calls and slower import times, especially for large datasets.
Consider making this configurable or increasing the default:
-const PAGE_LIMIT = 10; +const PAGE_LIMIT = 100; // or make it configurable +// const PAGE_LIMIT = process.env.TOLT_PAGE_LIMIT ? parseInt(process.env.TOLT_PAGE_LIMIT) : 100;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
apps/web/app/(ee)/api/cron/import/tolt/route.ts
(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx
(3 hunks)apps/web/lib/actions/partners/set-tolt-token.ts
(1 hunks)apps/web/lib/actions/partners/start-tolt-import.ts
(1 hunks)apps/web/lib/api/errors.ts
(1 hunks)apps/web/lib/tolt/api.ts
(1 hunks)apps/web/lib/tolt/import-affiliates.ts
(1 hunks)apps/web/lib/tolt/import-commissions.ts
(1 hunks)apps/web/lib/tolt/import-links.ts
(1 hunks)apps/web/lib/tolt/import-referrals.ts
(1 hunks)apps/web/lib/tolt/importer.ts
(1 hunks)apps/web/lib/tolt/schemas.ts
(1 hunks)apps/web/lib/tolt/types.ts
(1 hunks)apps/web/lib/tolt/update-stripe-customers.ts
(1 hunks)apps/web/ui/modals/import-tolt-modal.tsx
(1 hunks)apps/web/ui/modals/modal-provider.tsx
(6 hunks)packages/email/src/templates/campaign-imported.tsx
(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
apps/web/lib/tolt/import-links.ts (1)
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.
apps/web/lib/tolt/schemas.ts (1)
Learnt from: devkiran
PR: dubinc/dub#2433
File: apps/web/app/api/workspaces/[idOrSlug]/billing/payment-methods/route.ts:74-76
Timestamp: 2025-05-29T09:47:33.583Z
Learning: When Zod enum validation is used (e.g., `z.enum(PAYMENT_METHOD_TYPES)`), it ensures the validated value is one of the valid enum values, making subsequent type casts to the corresponding TypeScript types safe without additional validation.
apps/web/lib/tolt/import-referrals.ts (1)
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.
🧬 Code Graph Analysis (9)
apps/web/lib/actions/partners/set-tolt-token.ts (4)
apps/web/lib/actions/safe-action.ts (1)
authActionClient
(33-82)apps/web/lib/tolt/api.ts (1)
ToltApi
(18-157)apps/web/lib/tolt/types.ts (1)
ToltProgram
(22-24)apps/web/lib/tolt/importer.ts (1)
toltImporter
(54-54)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (2)
apps/web/ui/modals/import-tolt-modal.tsx (1)
useImportToltModal
(290-309)apps/web/ui/modals/import-rewardful-modal.tsx (1)
useImportRewardfulModal
(338-358)
apps/web/lib/actions/partners/start-tolt-import.ts (4)
apps/web/lib/actions/safe-action.ts (1)
authActionClient
(33-82)apps/web/lib/api/programs/get-default-program-id-or-throw.ts (1)
getDefaultProgramIdOrThrow
(4-17)apps/web/lib/api/programs/get-program-or-throw.ts (1)
getProgramOrThrow
(10-65)apps/web/lib/tolt/importer.ts (1)
toltImporter
(54-54)
apps/web/ui/modals/import-tolt-modal.tsx (3)
apps/web/lib/tolt/types.ts (1)
ToltProgram
(22-24)apps/web/lib/actions/partners/set-tolt-token.ts (1)
setToltTokenAction
(15-49)apps/web/lib/actions/partners/start-tolt-import.ts (1)
startToltImportAction
(13-45)
apps/web/ui/modals/modal-provider.tsx (1)
apps/web/ui/modals/import-tolt-modal.tsx (1)
useImportToltModal
(290-309)
apps/web/lib/tolt/types.ts (1)
apps/web/lib/tolt/schemas.ts (5)
ToltProgramSchema
(3-8)ToltAffiliateSchema
(10-19)ToltLinkSchema
(21-26)ToltCustomerSchema
(28-39)ToltCommissionSchema
(41-58)
apps/web/lib/tolt/import-commissions.ts (7)
apps/web/lib/tolt/importer.ts (2)
toltImporter
(54-54)MAX_BATCHES
(7-7)apps/web/lib/tolt/api.ts (1)
ToltApi
(18-157)packages/email/src/index.ts (1)
sendEmail
(6-29)packages/email/src/templates/campaign-imported.tsx (1)
CampaignImported
(17-65)apps/web/lib/tolt/types.ts (1)
ToltCommission
(32-32)packages/prisma/client.ts (1)
CommissionStatus
(4-4)apps/web/lib/api/partners/sync-total-commissions.ts (1)
syncTotalCommissions
(4-35)
apps/web/lib/tolt/importer.ts (1)
apps/web/lib/tolt/types.ts (1)
ToltConfig
(10-14)
apps/web/lib/tolt/api.ts (2)
apps/web/lib/tolt/types.ts (5)
ToltListResponse
(16-20)ToltAffiliate
(26-26)ToltLink
(28-28)ToltCustomer
(30-30)ToltCommission
(32-32)apps/web/lib/tolt/schemas.ts (5)
ToltProgramSchema
(3-8)ToltAffiliateSchema
(10-19)ToltLinkSchema
(21-26)ToltCustomerSchema
(28-39)ToltCommissionSchema
(41-58)
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts
[error] 97-97: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (23)
packages/email/src/templates/campaign-imported.tsx (1)
28-28
: LGTM! Clean type expansion for Tolt support.The union type properly extends the email template to support both Rewardful and Tolt providers. The existing template logic already handles the provider dynamically, so this change is all that's needed.
apps/web/lib/actions/partners/start-tolt-import.ts (1)
13-45
: Well-structured action with good validation.The action properly uses authentication middleware, validates program requirements (domain and URL), and follows the established error handling patterns. The logic flow is clear and appropriate.
apps/web/lib/actions/partners/set-tolt-token.ts (1)
15-49
: Excellent implementation with robust validation.The action properly:
- Enforces workspace-level permissions via
partnersEnabled
check- Validates credentials by actually calling the Tolt API
- Provides clear error messages for different failure scenarios
- Stores credentials only after successful validation
- Returns program data for UI feedback
The try/catch error handling is particularly well done, preserving the original error message while providing a fallback.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx (1)
6-6
: LGTM! Consistent implementation following established patterns.The Tolt import integration properly mirrors the existing Rewardful implementation:
- Hook usage and modal rendering follow the same pattern
- UI styling and structure are consistent
- Router navigation with query parameters matches the established approach
- Icon and text formatting align with existing import options
This maintains good code consistency and user experience.
Also applies to: 17-17, 25-25, 53-69
apps/web/app/(ee)/api/cron/import/tolt/route.ts (1)
1-53
: LGTM! Well-structured cron endpoint with proper authentication and error handling.The implementation correctly uses Qstash signature verification, validates input with Zod, and dispatches to appropriate import handlers.
apps/web/ui/modals/modal-provider.tsx (1)
30-30
: Perfect integration following the established modal pattern.The ImportToltModal is correctly integrated into the modal provider system, maintaining consistency with other modals.
Also applies to: 46-46, 57-57, 111-111, 203-203, 216-216
apps/web/lib/tolt/import-affiliates.ts (1)
1-77
: Well-implemented affiliate import with proper batching and error handling.The function correctly handles pagination, filters active affiliates, and manages the import queue.
apps/web/lib/tolt/types.ts (1)
1-32
: Clean and well-structured type definitions.Excellent use of Zod inference for type safety and proper generic typing for list responses.
apps/web/lib/tolt/importer.ts (3)
7-9
: LGTM! Well-defined constants.Good practice to define these constants at the module level for reuse across the Tolt import system.
11-17
: Excellent use of Zod enum for type safety.The
importSteps
enum provides both runtime validation and compile-time type safety across the import workflow.
19-52
: Solid implementation of ToltImporter class.The class provides clean abstractions for:
- Credential management with Redis caching
- Proper error handling for missing credentials
- Type-safe task queuing with QStash
The 24-hour cache expiry is appropriate for import credentials.
apps/web/lib/tolt/import-links.ts (3)
53-57
: Efficient partner filtering with Map lookup.Good performance optimization using Map for O(1) partner email lookup instead of array.find().
64-76
: Proper bulk link creation with required fields.The link mapping correctly includes all required fields and uses fallback generation for missing keys with
nanoid()
.
83-87
: Smart conditional queuing logic.Correctly queues either continuation of link import or progression to referral import based on pagination state.
apps/web/lib/tolt/schemas.ts (3)
3-8
: Clean program schema definition.Simple and focused schema capturing essential program metadata.
10-19
: Comprehensive affiliate schema with proper nullable fields.Good handling of optional fields like
company_name
andcountry_code
with nullable(), and proper default for status enum.
41-58
: Well-structured commission schema with smart field omission.Excellent use of
omit()
to prevent redundancy in nested objects while maintaining type safety. The monetary fields are correctly typed as strings (cents) with descriptive comments.apps/web/ui/modals/import-tolt-modal.tsx (3)
22-40
: Good modal state management with URL integration.Clean integration with search params to control modal visibility, providing better UX for bookmarkable states.
164-173
: Helpful external documentation links.Excellent UX providing direct links to Tolt settings where users can find their credentials.
282-282
: Smart import validation logic.Good business logic preventing imports when there are no affiliates to process.
apps/web/lib/tolt/import-commissions.ts (2)
218-285
: Comprehensive commission creation with proper stat updates.Excellent parallel processing of commission creation, sale recording, and stats updates. The status mapping and deduplication logic are well-implemented.
167-174
: Verify stripeCustomerId vs email usageI wasn’t able to find a
stripeCustomerId
field in the Prisma schema—please confirm that:• Under
model Customer
inpackages/prisma/schema/schema.prisma
there is astripeCustomerId
field of typeString?
(or similar) intended to store the Stripe customer ID (cus_…
).
• The code is deliberately usingcustomer.email
here as a fallback only if you don’t have a true Stripe ID—otherwise you should be passing the actualcus_…
identifier.If this is intentional, consider adding a comment to clarify the fallback behavior; otherwise update the query to use the proper Stripe ID field.
apps/web/lib/tolt/api.ts (1)
128-134
: Good handling of the unusual API response structure.The implementation correctly handles the nested
data.data
structure from the Tolt customers endpoint, and the comment clearly explains this API quirk for future maintainers.
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
♻️ Duplicate comments (1)
apps/web/lib/tolt/api.ts (1)
82-86
: Avoid sending empty string for optional pagination parameter.This issue was previously flagged but hasn't been addressed. Using
startingAfter || ""
sends an empty string when the parameter is undefined, which might not be the intended behavior for the API.Apply conditional parameter appending:
const searchParams = new URLSearchParams(); searchParams.append("program_id", programId); searchParams.append("expand[]", "program"); - searchParams.append("starting_after", startingAfter || ""); + if (startingAfter) { + searchParams.append("starting_after", startingAfter); + } searchParams.append("limit", PAGE_LIMIT.toString());This same pattern should be applied to all list methods (lines 105, 125, 148).
🧹 Nitpick comments (2)
apps/web/lib/tolt/api.ts (2)
26-43
: Improve error handling robustness.The current error handling assumes the error response is always JSON with a
message
property. This could fail if the API returns different error formats (e.g., plain text, HTML, or different JSON structure).private async fetch<T>(url: string): Promise<T> { const response = await fetch(url, { headers: { Authorization: `Bearer ${this.token}`, }, }); if (!response.ok) { - const error = await response.json(); - - console.error("Tolt API Error:", error); - throw new Error(error.message || "Unknown error from Tolt API."); + let errorMessage = `HTTP ${response.status}: ${response.statusText}`; + + try { + const error = await response.json(); + errorMessage = error.message || error.error || errorMessage; + } catch { + // If JSON parsing fails, use the HTTP status message + } + + console.error("Tolt API Error:", errorMessage); + throw new Error(errorMessage); } const data = await response.json(); return data as T; }
128-134
: Document the API inconsistency workaround.The nested
data.data
structure is handled correctly, but this API inconsistency makes the code fragile to future API changes.Consider adding more detailed documentation about this API quirk:
- // This might be an issue with the Tolt response, the response is within data.data for this endpoint + // WORKAROUND: The Tolt /customers endpoint returns a nested structure { data: { data: [...] } } + // unlike other endpoints that return { data: [...] }. This may change in future API versions. const { data } = await this.fetch<{ data: { data: ToltCustomer[] } }>( `${this.baseUrl}/customers?${searchParams.toString()}`, );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/lib/tolt/api.ts
(1 hunks)apps/web/lib/tolt/import-affiliates.ts
(1 hunks)apps/web/lib/tolt/import-links.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/tolt/import-affiliates.ts
- apps/web/lib/tolt/import-links.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/lib/tolt/api.ts (2)
apps/web/lib/tolt/types.ts (5)
ToltListResponse
(16-20)ToltAffiliate
(26-26)ToltLink
(28-28)ToltCustomer
(30-30)ToltCommission
(32-32)apps/web/lib/tolt/schemas.ts (5)
ToltProgramSchema
(3-8)ToltAffiliateSchema
(10-19)ToltLinkSchema
(21-26)ToltCustomerSchema
(28-39)ToltCommissionSchema
(41-58)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/web/lib/tolt/api.ts (1)
45-73
: Well-implemented workaround with proper error handling.The approach of using the partners endpoint to fetch program details is well-documented and handles edge cases appropriately. Good use of Zod validation and error handling.
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
♻️ Duplicate comments (2)
apps/web/lib/tolt/update-stripe-customers.ts (1)
108-115
: Security vulnerability exists: Email not properly escaped in Stripe search query.This is a duplicate of a previous review comment. The email is directly interpolated into the search query without proper escaping, which could cause issues with special characters or potential injection attacks.
Apply this fix to properly escape the email:
const stripeCustomers = await stripe.customers.search( { - query: `email:'${customer.email}'`, + query: `email:"${customer.email.replace(/["\\]/g, '\\$&')}"`, }, { stripeAccount: workspace.stripeConnectId!, }, );apps/web/lib/tolt/import-commissions.ts (1)
8-8
: Import inconsistency: Using rewardful MAX_BATCHES instead of tolt.This is a duplicate of a previous review comment. You're importing
MAX_BATCHES
from the rewardful importer instead of the tolt importer, which could lead to inconsistent behavior.-import { MAX_BATCHES } from "../rewardful/importer"; +import { MAX_BATCHES } from "./importer";
🧹 Nitpick comments (3)
apps/web/lib/tolt/import-commissions.ts (3)
106-106
: Use optional chain for safer property access.The static analysis correctly identifies that this can be simplified with optional chaining.
- if (workspaceUser && workspaceUser.user.email) { + if (workspaceUser?.user?.email) {
155-176
: Consider removing commented code if not needed.This large block of commented code appears to be from a previous implementation (possibly Rewardful). If it's no longer needed for the Tolt implementation, consider removing it to improve code cleanliness.
If this code might be useful for reference or future implementation, consider adding a comment explaining its purpose.
235-235
: Consider making currency configurable.The currency is hardcoded to "usd" with TODO comments indicating this should be dynamic. While this is acceptable for the initial implementation, consider adding this to the backlog for future enhancement.
Consider creating a tracking issue for dynamic currency support.
Also applies to: 250-250
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/lib/tolt/import-affiliates.ts
(1 hunks)apps/web/lib/tolt/import-commissions.ts
(1 hunks)apps/web/lib/tolt/import-referrals.ts
(1 hunks)apps/web/lib/tolt/schemas.ts
(1 hunks)apps/web/lib/tolt/update-stripe-customers.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/lib/tolt/import-referrals.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/tolt/import-affiliates.ts
- apps/web/lib/tolt/schemas.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/lib/tolt/update-stripe-customers.ts (3)
apps/web/lib/stripe/index.ts (2)
stripe
(3-9)stripeAppClient
(12-22)apps/web/lib/tolt/importer.ts (2)
MAX_BATCHES
(7-7)toltImporter
(54-54)packages/utils/src/functions/log.ts (1)
log
(9-45)
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts
[error] 106-106: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (18)
apps/web/lib/tolt/update-stripe-customers.ts (6)
1-11
: LGTM! Clean imports and setup.The imports are well-organized and the Stripe client initialization correctly handles environment-specific configuration.
22-42
: LGTM! Proper workspace validation.Good defensive programming by checking for the presence of
stripeConnectId
before proceeding with Stripe operations.
47-88
: LGTM! Well-implemented batch processing with proper pagination.The batch processing logic correctly handles:
- Pagination using cursor-based approach
- Rate limiting with 2-second delays between batches
- Proper exit conditions
The implementation ensures efficient processing while respecting API rate limits.
90-98
: LGTM! Proper continuation of processing when more data exists.The queuing mechanism correctly preserves the cursor position to continue processing from where it left off.
117-133
: LGTM! Appropriate error handling for Stripe search results.The function correctly handles both no results and multiple results cases. Logging multiple customers as an error is appropriate since each email should ideally map to a single Stripe customer.
137-144
: LGTM! Clean database update operation.The database update correctly sets the Stripe customer ID when exactly one customer is found.
apps/web/lib/tolt/import-commissions.ts (12)
16-22
: LGTM! Clean status mapping.The status mapping from Tolt to Dub commission statuses is clear and comprehensive.
31-45
: LGTM! Proper setup and credential retrieval.The function correctly retrieves program details and credentials from the centralized importer.
51-76
: LGTM! Well-implemented batch processing pattern.The batch processing follows the same pattern as the Stripe customer update function, with proper pagination and rate limiting.
78-86
: LGTM! Proper continuation logic.The queuing mechanism correctly handles continuation when more batches need to be processed.
88-88
: LGTM! Proper cleanup of credentials.Credentials are correctly deleted after processing is complete.
107-117
: LGTM! Proper notification email sending.The email notification correctly uses the updated template with Tolt provider support.
119-122
: LGTM! Proper queuing of follow-up task.The function correctly queues the Stripe customer update task after commission import completion.
137-149
: LGTM! Proper duplicate prevention.The function correctly checks for existing commissions by invoice ID to prevent duplicates.
178-204
: LGTM! Comprehensive validation of required data.The function properly validates that all required relationships (customer, link, click, partner) exist before proceeding with commission creation.
206-220
: LGTM! Proper lead event retrieval and validation.The function correctly retrieves and validates lead event data, with proper schema parsing.
223-284
: LGTM! Comprehensive commission creation with proper side effects.The function correctly creates the commission record and handles all necessary side effects:
- Commission record creation
- Sale event recording
- Link statistics updates
- Customer statistics updates
The use of
Promise.all
ensures these operations happen concurrently for better performance.
286-289
: LGTM! Proper commission synchronization.The function correctly syncs total commissions for the partner after creating the commission.
@CodeRabbit final review |
Tip For best results, initiate chat on the files or code changes. @steven-tey Here's my final review summary of the Tolt importer implementation: 🎯 Critical Issues That Must Be Addressed1. Database Transaction Safety ❌Risk Level: HIGH // Current approach in import-commissions.ts - NOT atomic
await Promise.all([
prisma.commission.create(/* ... */),
recordSaleWithTimestamp(/* ... */),
prisma.link.update(/* ... */),
prisma.customer.update(/* ... */),
]);
// Should be:
await prisma.$transaction(async (tx) => {
// All operations here
}); Impact: Partial imports if any operation fails mid-process. 2. Production Logging
|
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/tests/embed-tokens/referrals.test.ts (1)
83-97
: Robust validation testing for required parameters.The test properly validates that at least one of partnerId, tenantId, or partner must be provided. The error message assertion could be more specific to avoid brittleness if the exact wording changes.
Consider using a more flexible error message assertion:
- message: expect.stringContaining( - "You must provide either partnerId, tenantId, or partner", - ), + message: expect.stringMatching(/partnerId|tenantId|partner/),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/tests/embed-tokens/referrals.test.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/tests/embed-tokens/referrals.test.ts (2)
apps/web/tests/utils/resource.ts (1)
E2E_PARTNER
(64-67)apps/web/tests/utils/helpers.ts (2)
randomEmail
(23-29)randomId
(4-4)
⏰ 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 (10)
apps/web/tests/embed-tokens/referrals.test.ts (10)
1-10
: Well-structured test setup with good helper usage.The imports and test setup are clean and appropriate. The use of helper functions for generating random data helps avoid test conflicts, and the expected token response structure provides good validation.
12-16
: Good use of sequential testing for stateful operations.The sequential test execution is appropriate here since you're creating a partner in one test and using it in subsequent tests. The integration harness pattern provides a clean way to handle HTTP requests.
18-28
: Solid baseline test using existing partner.Good starting point that validates the endpoint works with an existing partner. The test structure and assertions are clear.
30-52
: Comprehensive partner creation test with good state management.The test covers creating a new partner with detailed properties and properly stores the tenantId for use in subsequent tests. The partner object includes all relevant fields for a thorough test.
54-65
: Effective test of tenantId lookup functionality.This test validates that the endpoint can find partners by tenantId, which is important for the referral flow. The dependency on the previous test's state is well-documented.
67-81
: Good minimal case testing.Testing with only the required email field ensures the endpoint handles minimal partner creation correctly.
99-114
: Proper error handling for non-existent partners.Good coverage of the 404 case with appropriate error message and code validation.
116-131
: Consistent error handling for non-existent tenants.The test properly validates the same error response for non-existent tenantId, maintaining consistency with the partnerId error case.
133-149
: Appropriate email validation testing.The test validates that invalid email addresses are rejected with proper error messages.
151-167
: Complete validation coverage for required fields.Good final test that ensures email is required when creating a partner. The test suite provides comprehensive coverage of both success and failure scenarios.
…nt; update Form component to use new type for selectedSource.
…ic import source selection; refactor affiliate count handling in Tolt and Rewardful forms to use 'affiliates' instead of 'total_affiliates'. Update UI components to reflect these changes.
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: 2
🧹 Nitpick comments (3)
apps/web/lib/actions/partners/create-program.ts (1)
156-162
: Apply optional chaining for cleaner code.The static analysis tool correctly suggests using optional chaining to simplify the condition.
- if (tolt && tolt.id) { + if (tolt?.id) {apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx (1)
180-180
: Consider safer property access.While the current logic ensures
affiliates
exists when this renders, using optional chaining would be more defensive.- {toltProgram.affiliates?.toLocaleString() || "0"} + {(toltProgram.affiliates ?? 0).toLocaleString()}apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (1)
99-109
: Use optional chaining for cleaner code.The logic is correct, but the static analysis tool correctly suggests using optional chaining for more concise code.
Apply this diff to use optional chaining:
- if (rewardful && rewardful.id) { + if (rewardful?.id) { setSelectedSource(PROGRAM_IMPORT_SOURCES[0]); - } else if (tolt && tolt.id) { + } else if (tolt?.id) { setSelectedSource(PROGRAM_IMPORT_SOURCES[1]); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx
(5 hunks)apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx
(5 hunks)apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx
(1 hunks)apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx
(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx
(3 hunks)apps/web/lib/actions/partners/create-program.ts
(3 hunks)apps/web/lib/partners/constants.ts
(1 hunks)apps/web/lib/tolt/api.ts
(1 hunks)apps/web/lib/tolt/types.ts
(1 hunks)apps/web/lib/zod/schemas/program-onboarding.ts
(2 hunks)apps/web/ui/modals/import-tolt-modal.tsx
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- apps/web/lib/partners/constants.ts
- apps/web/ui/modals/import-tolt-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx
- apps/web/lib/tolt/types.ts
- apps/web/lib/tolt/api.ts
🧰 Additional context used
🧠 Learnings (2)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (2)
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.
Learnt from: TWilson023
PR: dubinc/dub#2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
apps/web/lib/actions/partners/create-program.ts (1)
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.
🧬 Code Graph Analysis (4)
apps/web/lib/zod/schemas/program-onboarding.ts (1)
apps/web/lib/tolt/schemas.ts (1)
ToltProgramSchema
(3-8)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (1)
apps/web/lib/partners/constants.ts (1)
PROGRAM_IMPORT_SOURCES
(64-77)
apps/web/lib/actions/partners/create-program.ts (1)
apps/web/lib/tolt/importer.ts (1)
toltImporter
(90-90)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (1)
apps/web/lib/types.ts (1)
ProgramData
(476-476)
🪛 Biome (1.9.4)
apps/web/lib/actions/partners/create-program.ts
[error] 157-157: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx
[error] 102-102: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 104-104: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ 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 (11)
apps/web/lib/zod/schemas/program-onboarding.ts (1)
1-1
: LGTM: Well-structured schema extension.The Tolt schema integration follows the established pattern used for Rewardful, properly extending the base schema with the required
maskedToken
andaffiliates
fields while maintaining nullish optionality.Also applies to: 33-36
apps/web/lib/actions/partners/create-program.ts (1)
8-8
: LGTM: Consistent import integration pattern.The Tolt importer integration follows the same reliable pattern established for Rewardful, maintaining consistency in the codebase.
Also applies to: 46-46
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/partners/form.tsx (2)
4-4
: LGTM: Excellent refactoring for multiple import sources.The generalization to support both Rewardful and Tolt import sources is well-implemented, using the
PROGRAM_IMPORT_SOURCES
constant for maintainable, dynamic rendering logic.Also applies to: 41-41, 68-73
77-77
: LGTM: Clean dynamic UI rendering.The conditional rendering based on
importSource
properly displays the appropriate import metadata and affiliate counts for either Rewardful or Tolt sources.Also applies to: 87-89, 97-97
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-tolt-form.tsx (2)
21-47
: LGTM: Well-designed two-step import flow.The component implements a clean two-step flow with proper state management. The
useEffect
correctly synchronizes form state with component state when data is already present.
98-104
: LGTM: Proper security handling for API tokens.Using
type="password"
for the API token input appropriately masks sensitive credentials in the UI.apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx (2)
27-71
: LGTM: Solid form implementation with proper state management.The component effectively handles token validation, campaign fetching, and form state synchronization with good separation of concerns.
66-70
: LGTM: Robust commission formatting logic.The
formatCommission
callback properly handles both percentage and dollar amount formatting with appropriate decimal precision.apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (3)
5-5
: LGTM! Good modularization of import functionality.The new imports support the multi-source import feature well. Moving the import logic to dedicated components (
ImportRewardfulForm
andImportToltForm
) improves code organization and maintainability.Also applies to: 10-15, 20-20, 28-29
63-64
: Clean state management for multi-source imports.The type definition properly derives from the constant, and the state management correctly handles both import sources. Good job ensuring data is cleared when switching program types.
Also applies to: 69-71, 81-86, 91-91
141-143
: Correct validation logic for multi-source imports.The button state logic properly ensures at least one valid import source is selected before allowing submission.
Also applies to: 147-148
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-rewardful-form.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx
Show resolved
Hide resolved
@CodeRabbit final review |
Tip For best results, initiate chat on the files or code changes. @steven-tey, here's my final verdict on the Tolt importer PR: 🎯 Final Decision: APPROVE WITH CONDITIONSCritical Blockers Before Production:
Overall Assessment:
Recommendation:✅ MERGE - This is a high-quality implementation with solid foundations. The critical issues are straightforward fixes that don't affect the core architecture. Risk Level: Low (after transaction fixes) The feature is well-architected and production-ready pending the transaction safety fixes. Great work on this comprehensive integration! 🐰✨ |
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Developer Experience
Tests