-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Tolt import 8000 er #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
base: main
Are you sure you want to change the base?
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.)
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 (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 = a 67E6 wait 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.
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: 3
♻️ Duplicate comments (3)
apps/web/lib/tolt/import-commissions.ts (2)
157-182
: Remove the commented code block.This large commented block appears to be unused duplicate checking logic. It should be removed to keep the codebase clean.
- // 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; - // }
244-244
: Address the currency handling TODOs.The hardcoded "usd" currency needs to be resolved. Since Tolt's API doesn't provide currency information, consider implementing a workspace-level currency configuration or making the currency field optional in the schema.
Consider one of these approaches:
- Add a workspace-level currency setting that can be configured during import setup
- Add an environment variable for default currency
- Extend the
ToltCommissionSchema
to include an optional currency field- currency: "usd", // TODO: there is no currency in the commission + currency: workspace.currency || "usd", // Use workspace currency or default to USDAlso applies to: 259-259
apps/web/lib/tolt/import-affiliates.ts (1)
38-41
: Verify reward uniqueness constraints.The code selects the first reward of each type without checking for duplicates. Please verify that programs are constrained to have at most one reward per event type.
🧹 Nitpick comments (4)
apps/web/lib/tolt/import-commissions.ts (1)
105-105
: Use optional chaining for cleaner null checks.Simplify the conditional check using optional chaining as suggested by static analysis.
- if (workspaceUser && workspaceUser.user.email) { + if (workspaceUser?.user.email) {apps/web/lib/tolt/import-affiliates.ts (3)
59-59
: Fix misleading variable name.The variable
partnersPromise
is not a promise - it's the resolved result fromPromise.allSettled()
.- const partnersPromise = await Promise.allSettled( + const partnerResults = await Promise.allSettled(Update line 69 accordingly:
- const partners = partnersPromise + const partners = partnerResults
81-81
: Consider making the delay configurable.The hardcoded 2-second delay might not be appropriate for all scenarios. Consider making this configurable or implementing exponential backoff for rate limiting.
+const BATCH_DELAY_MS = 2000; // Consider making this configurable + export async function importAffiliates({ ... - await new Promise((resolve) => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS));
115-117
: Consider updating partner information on upsert.The update block is empty, which means existing partners won't get their information updated even if it changed in Tolt (e.g., name, company, country).
update: { - // do nothing + name: [affiliate.first_name, affiliate.last_name] + .filter(Boolean) + .join(" ") + .trim() || "Unknown", + companyName: affiliate.company_name, + country: affiliate.country_code, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
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)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/tolt/import-referrals.ts
🧰 Additional context used
🧠 Learnings (2)
apps/web/lib/tolt/import-affiliates.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/import-commissions.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2484
File: apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/usage/get-recommended-plan.ts:11-12
Timestamp: 2025-06-25T18:08:53.931Z
Learning: In the Dub codebase, prefer keeping semantically different functions separate even if they currently have identical implementations. This maintains readability and allows for easy future divergence when business logic changes. For example, `hasConversions` and `hasPartners` functions should remain separate despite having the same current logic, as they serve different conceptual purposes and may need different feature checks in the future.
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts
[error] 105-105: 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 (3)
apps/web/lib/tolt/import-commissions.ts (3)
1-21
: LGTM! Imports and status mapping are well-structured.The imports are properly organized and the status mapping logic correctly converts Tolt commission statuses to Dub's CommissionStatus enum. The mapping decisions make business sense (e.g., both "pending" and "approved" Tolt statuses map to "pending" in Dub).
23-122
: Well-structured import function with good error handling.The pagination logic, parallel processing with
Promise.allSettled
, and queue-based continuation are well implemented. The 2-second delay between batches provides good rate limiting.
125-299
: Solid commission creation logic with comprehensive validation.The function implements thorough validation steps and uses parallel database operations effectively. The error handling with early returns keeps the code clean and prevents invalid data processing.
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: 1
♻️ Duplicate comments (1)
apps/web/lib/tolt/import-commissions.ts (1)
244-244
: Address the currency handling TODOs.The hardcoded "usd" currency and TODO comments indicate missing currency support from Tolt's API response. Consider implementing a workspace/program-level currency configuration or investigating if Tolt's API includes currency information.
Since the
ToltCommissionSchema
doesn't include a currency field, implement one of these solutions:
- Add a workspace/program-level currency configuration
- Extend the schema to include an optional currency field
- Use environment variables for default currency settings
- currency: "usd", // TODO: there is no currency in the commission + currency: program.currency || "usd", // Use program-level currency or defaultAlso applies to: 259-259
🧹 Nitpick comments (3)
apps/web/lib/tolt/import-commissions.ts (3)
105-105
: Use optional chaining for safer property access.The condition can be simplified using optional chaining as suggested by the static analysis tool.
- if (workspaceUser && workspaceUser.user.email) { + if (workspaceUser?.user.email) {
71-71
: Consider making the rate limiting delay configurable.The 2-second delay is hardcoded. Consider making this configurable through environment variables or constants to allow adjustment based on API rate limits.
+const BATCH_DELAY_MS = parseInt(process.env.TOLT_BATCH_DELAY_MS || "2000"); + - await new Promise((resolve) => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS));
247-247
: Consider clarifying the transaction ID usage in comments.The comment accurately explains that
transaction_id
is not the actual invoice ID but is used for deduplication. Consider enhancing this comment to explain why this approach was chosen.- invoiceId: sale.transaction_id, // this is not the actual invoice ID, but we use this to deduplicate the sales + invoiceId: sale.transaction_id, // Tolt transaction_id used for deduplication since actual invoice ID not available
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/lib/api/partners/create-partner-link.ts
(3 hunks)apps/web/lib/tolt/cleanup-partners.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)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/tolt/cleanup-partners.ts
- apps/web/lib/tolt/import-referrals.ts
🧰 Additional context used
🧠 Learnings (3)
apps/web/lib/api/partners/create-partner-link.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/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/import-commissions.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2484
File: apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/usage/get-recommended-plan.ts:11-12
Timestamp: 2025-06-25T18:08:53.931Z
Learning: In the Dub codebase, prefer keeping semantically different functions separate even if they currently have identical implementations. This maintains readability and allows for easy future divergence when business logic changes. For example, `hasConversions` and `hasPartners` functions should remain separate despite having the same current logic, as they serve different conceptual purposes and may need different feature checks in the future.
🪛 Biome (1.9.4)
apps/web/lib/tolt/import-commissions.ts
[error] 105-105: 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 (8)
apps/web/lib/tolt/import-commissions.ts (3)
177-202
: Excellent duplicate detection logic implementation.The duplicate checking logic using customer ID, amount, and time window (±1 hour) is well-implemented. This addresses the transition period scenario effectively by preventing duplicate commissions during imports.
61-69
: Good use of Promise.allSettled for concurrent processing.Using
Promise.allSettled
ensures that a failure in processing one commission doesn't block the processing of others, which is appropriate for batch import operations.
232-293
: Well-structured parallel database operations.The use of
Promise.all
to execute commission creation, event recording, and stats updates concurrently is efficient and properly structured. The separation of concerns between commission tracking and statistics updates is well-implemented.apps/web/lib/api/partners/create-partner-link.ts (2)
26-26
: LGTM: Optional key parameter added correctly.The addition of the optional
key
parameter maintains backward compatibility while enabling custom key specification for partner links, which is essential for the Tolt integration.
76-85
: LGTM: Key generation logic properly updated.The updated logic correctly prioritizes the provided
key
parameter while maintaining the existing fallback behavior. The implementation follows the established pattern of username → name → email prefix for key generation.apps/web/lib/tolt/import-links.ts (3)
9-95
: LGTM: Well-structured import function with proper pagination and error handling.The
importLinks
function is well-implemented with:
- Efficient batch processing with pagination
- Proper filtering to only process links with existing partners
- Correct queue management for the import workflow
- Good error boundaries and logging
The implementation follows established patterns in the codebase.
71-74
: Defensive programming for partner lookup.The partner lookup check is good defensive programming, even though it should theoretically never happen due to the filtering logic. The console.log helps with debugging and monitoring.
128-135
: Correct usage of the enhanced generatePartnerLink function.The function properly utilizes the newly added
key
parameter from the updatedgeneratePartnerLink
function, passinglink.value
as the custom key. This ensures imported links maintain their original keys from Tolt.
@coderabbitai full review please |
C95D
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainI'll conduct a comprehensive review of this Tolt importer implementation. Let me examine the code in detail to provide thorough feedback. 🏁 Script executed:
Length of output: 1752 🏁 Script executed:
Length of output: 4507 🏁 Script executed:
Length of output: 2177 🏁 Script executed:
Length of output: 3462 🏁 Script executed:
Length of output: 7239 🏁 Script executed:
Length of output: 9265 🏁 Script executed:
Length of output: 1498 @devkiran I've conducted a comprehensive review of the Tolt importer implementation. This is a well-structured feature with good separation of concerns. Here are my findings: 🔒 Security & Authentication✅ Strengths:
🚀 Performance & Scalability✅ Strengths:
🔄 Data Integrity & Transactions❌ Critical Issue - Missing Database Transactions: 🏁 Script executed:
Length of output: 198 🏁 Script executed:
Length of output: 1804 🏁 Script executed:
Length of output: 1673 🔄 Data Integrity & Transactions❌ Critical Issue - Missing Database Transactions: // In import-commissions.ts, this should be wrapped in a transaction:
await Promise.all([
prisma.commission.create({ /* ... */ }),
recordSaleWithTimestamp({ /* ... */ }),
prisma.link.update({ /* ... */ }),
prisma.customer.update({ /* ... */ }),
]); Recommendation: Wrap related database operations in transactions: await prisma.$transaction(async (tx) => {
// All related operations here
}); 🐛 Error Handling & Logging
Recommendations:
🎯 Code Quality & Best Practices✅ Strengths:
🔗 API Integration✅ Strengths:
🎨 UI/UX Implementation✅ Strengths:
✅ Security in UI:
📊 Performance Considerations✅ Excellent Batching Strategy:
Recommendation: Consider making batch sizes configurable based on API response times. 🔒 Security Assessment✅ Strong Security Measures:
Recommendations for Enhancement:
🎯 Overall AssessmentThis is a well-architected feature that follows good patterns and includes comprehensive error handling. The main areas for improvement are:
The implementation demonstrates strong understanding of Next.js patterns, TypeScript, and external API integration. Great work on the comprehensive validation and user experience! 🐰✨ |
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Developer Experience