8000 Tolt importer by devkiran · Pull Request #2600 · dubinc/dub · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open

Tolt importer #2600

wants to merge 55 commits into from

Conversation

devkiran
Copy link
Collaborator
@devkiran devkiran commented Jul 2, 2025

Summary by CodeRabbit

  • New Features

    • Added support for importing partners, links, referrals, commissions, and Stripe customer synchronization from Tolt, including a new import option and modal in the dashboard.
    • Enabled authentication and storage of Tolt API credentials.
    • Added automated partner cleanup for unused partners after import.
  • Improvements

    • Enhanced import progress visibility and error handling with user notifications.
    • Updated email notifications to support Tolt as a provider.
  • Bug Fixes

    • Improved error logging for API errors.
  • Developer Experience

    • Introduced comprehensive data validation, typing, and API integration for Tolt.
    • Improved partner link creation with customizable keys.

Copy link
vercel bot commented Jul 2, 2025

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

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Jul 5, 2025 6:14am

Copy link
Contributor
coderabbitai bot commented Jul 2, 2025

Walkthrough

This 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

Files/Paths Change Summary
apps/web/app/(ee)/api/cron/import/tolt/route.ts New API route for handling Tolt import/update actions, signature verification, and action dispatch.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/import-export-buttons.tsx Added Tolt import option, modal hook, and UI integration for importing partners from Tolt.
apps/web/lib/actions/partners/set-tolt-token.ts
apps/web/lib/actions/partners/start-tolt-import.ts
New server actions for setting Tolt credentials and starting Tolt import.
apps/web/lib/api/errors.ts Simplified error logging in handleApiError.
apps/web/lib/tolt/api.ts New ToltApi class for interacting with Tolt API endpoints and parsing responses.
apps/web/lib/tolt/import-affiliates.ts
apps/web/lib/tolt/import-links.ts
apps/web/lib/tolt/import-referrals.ts
apps/web/lib/tolt/import-commissions.ts
apps/web/lib/tolt/update-stripe-customers.ts
apps/web/lib/tolt/cleanup-partners.ts
New modules for importing affiliates, links, referrals, commissions, updating Stripe customers, and cleaning up partners from Tolt.
apps/web/lib/tolt/importer.ts New ToltImporter class for credential management, import queuing, and partner ID tracking.
apps/web/lib/tolt/schemas.ts New Zod schemas for Tolt program, affiliate, link, customer, and commission entities.
apps/web/lib/tolt/types.ts New TypeScript types and interfaces for Tolt entities and config.
apps/web/ui/modals/import-tolt-modal.tsx New modal component and hook for Tolt import, with step-based UI and actions.
apps/web/ui/modals/modal-provider.tsx Integrated Tolt import modal into modal context and provider.
packages/email/src/templates/c 8000 ampaign-imported.tsx Expanded provider prop in CampaignImported email template to accept "Tolt".
apps/web/lib/api/partners/create-partner-link.ts Extended PartnerLinkArgs type and generatePartnerLink to accept optional custom key property.

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.)
Loading

Suggested reviewers

  • steven-tey

Poem

A Tolt import hops into view,
With modals and queues, and APIs too!
Affiliates, links, and partners align,
Stripe IDs updated, all working fine.
From schemas to types, the groundwork is set—
This bunny’s delighted, it’s Tolt we now get!
🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ffa5d8d and 1678238.

📒 Files selected for processing (1)
  • apps/web/lib/tolt/update-stripe-customers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/tolt/update-stripe-customers.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need 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)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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:

  1. Adding currency field to the Tolt schemas
  2. Fetching currency from program/workspace settings
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 26d76fa and 33cb5d0.

📒 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 and country_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 usage

I wasn’t able to find a stripeCustomerId field in the Prisma schema—please confirm that:

• Under model Customer in packages/prisma/schema/schema.prisma there is a stripeCustomerId field of type String? (or similar) intended to store the Stripe customer ID (cus_…).
• The code is deliberately using customer.email here as a fallback only if you don’t have a true Stripe ID—otherwise you should be passing the actual cus_… 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.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 33cb5d0 and 8f926c0.

📒 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.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8f926c0 and bad2606.

📒 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.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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:

  1. Add a workspace-level currency setting that can be configured during import setup
  2. Add an environment variable for default currency
  3. 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 USD

Also 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 from Promise.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

📥 Commits

Reviewing files that changed from the base of the PR and between 62f17d8 and b3b9d95.

📒 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.

@devkiran devkiran requested a review from steven-tey July 4, 2025 06:32
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (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:

  1. Add a workspace/program-level currency configuration
  2. Extend the schema to include an optional currency field
  3. 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 default

Also 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

📥 Commits

Reviewing files that changed from the base of the PR and between d143ae2 and ffa5d8d.

📒 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 updated generatePartnerLink function, passing link.value as the custom key. This ensures imported links maintain their original keys from Tolt.

@devkiran
Copy link
Collaborator Author
devkiran commented Jul 4, 2025

@coderabbitai full review please

Copy link
Contributor
coderabbitai bot commented Jul 4, 2025
C95D

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'll conduct a comprehensive review of this Tolt importer implementation. Let me examine the code in detail to provide thorough feedback.


🏁 Script executed:

#!/bin/bash
# First, let's examine the main API route handler for security and implementation
cat apps/web/app/\(ee\)/api/cron/import/tolt/route.ts

Length of output: 1752


🏁 Script executed:

#!/bin/bash
# Let's examine the Tolt API client for security and best practices
cat apps/web/lib/tolt/api.ts

Length of output: 4507


🏁 Script executed:

#!/bin/bash
# Let's examine the importer credential management and queuing logic
cat apps/web/lib/tolt/importer.ts

Length of output: 2177


🏁 Script executed:

#!/bin/bash
# Let's examine the import-affiliates module for data handling and security
cat apps/web/lib/tolt/import-affiliates.ts

Length of output: 3462


🏁 Script executed:

#!/bin/bash
# Let's examine the import-commissions module as it likely has the most complex business logic
cat apps/web/lib/tolt/import-commissions.ts

Length of output: 7239


🏁 Script executed:

#!/bin/bash
# Let's examine the modal component for UI and security considerations
cat apps/web/ui/modals/import-tolt-modal.tsx

Length of output: 9265


🏁 Script executed:

#!/bin/bash
# Let's examine the Zod schemas for data validation
cat apps/web/lib/tolt/schemas.ts

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:

  • Proper QStash signature verification in the API route
  • API credentials stored in Redis with 24-hour expiry (good for temporary storage)
  • Input validation using Zod schemas throughout
  • API keys handled as password inputs in the UI

⚠️ Recommendations:

  • Consider encrypting API tokens before storing in Redis
  • Add rate limiting to the modal API actions to prevent abuse

🚀 Performance & Scalability

✅ Strengths:

  • Excellent batching strategy with MAX_BATCHES = 5
  • Proper pagination using cursor-based approach
  • Rate limiting with 2-second delays between API calls
  • Efficient use of Promise.allSettled for concurrent operations

⚠️ Areas for improvement:

  • Consider adding retry logic for transient API failures
  • The commission import has complex duplicate detection logic that could be optimized

🔄 Data Integrity & Transactions

❌ Critical Issue - Missing Database Transactions:


🏁 Script executed:

#!/bin/bash
# Let's check if there are any database transactions in the import modules
grep -n "prisma\.\$transaction\|BEGIN\|COMMIT" apps/web/lib/tolt/import-*.ts

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Let's examine the cleanup-partners module for transaction usage
cat apps/web/lib/tolt/cleanup-partners.ts

Length of output: 1804


🏁 Script executed:

#!/bin/bash
# Let's examine error handling patterns and check for potential memory issues
grep -n "console.log\|console.error" apps/web/lib/tolt/*.ts

Length of output: 1673


🔄 Data Integrity & Transactions

❌ Critical Issue - Missing Database Transactions:
The import modules lack proper database transactions, which could lead to partial imports if failures occur:

// 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

⚠️ Issues Found:

  • Extensive use of console.log instead of proper logging
  • Some operations silently skip errors without alerting users
  • Import operations continue even when individual records fail

Recommendations:

  • Replace console.log with structured logging
  • Add proper error aggregation and reporting
  • Consider implementing a retry mechanism for transient failures

🎯 Code Quality & Best Practices

✅ Strengths:

  • Excellent TypeScript usage and type safety
  • Good separation of concerns across modules
  • Proper Zod schema validation throughout
  • Clean, readable code structure

⚠️ Minor Issues:

  • Hardcoded currency values ("usd") should be configurable
  • API response structure inconsistency in listCustomers (nested data.data)
  • Some magic numbers could be extracted to constants

🔗 API Integration

✅ Strengths:

  • Robust error handling in ToltApi class
  • Proper Bearer token authentication
  • Good pagination implementation
  • Schema validation for all API responses

⚠️ Recommendations:

  • Add timeout configuration for API calls
  • Consider implementing exponential backoff for retries
  • Handle rate limiting responses from Tolt API

🎨 UI/UX Implementation

✅ Strengths:

  • Progressive disclosure with step-by-step flow
  • Proper form validation and loading states
  • Good accessibility with proper labels and focus management
  • Helpful external links for finding credentials

✅ Security in UI:

  • API key input uses type="password"
  • Proper cleanup of URL parameters

📊 Performance Considerations

✅ Excellent Batching Strategy:

  • Smart use of MAX_BATCHES = 5 to prevent timeouts
  • Proper pagination with cursor-based approach
  • Rate limiting with 2-second delays

Recommendation: Consider making batch sizes configurable based on API response times.

🔒 Security Assessment

✅ Strong Security Measures:

  • QStash signature verification prevents unauthorized access
  • Input validation throughout the pipeline
  • Credentials expire after 24 hours

Recommendations for Enhancement:

  • Encrypt API tokens before Redis storage
  • Add rate limiting to server actions
  • Consider audit logging for import operations

🎯 Overall Assessment

This is a well-architected feature that follows good patterns and includes comprehensive error handling. The main areas for improvement are:

  1. Add database transactions for data integrity
  2. Improve logging and error reporting
  3. Add retry mechanisms for resilience

The implementation demonstrates strong understanding of Next.js patterns, TypeScript, and external API integration. Great work on the comprehensive validation and user experience!

🐰✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0