-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Rewardful import coupons #2963
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
Rewardful import coupons #2963
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
💡 Enable Vercel Agent with $100 free credit for automated AI reviews |
WalkthroughThis PR extends the Rewardful import workflow to support importing affiliate coupons. It adds API support for fetching coupons from Rewardful, integrates coupons into the referral data model, persists affiliate-to-partner mappings in Redis, and chains the coupon import as a follow-up step after partner creation. Changes
Sequence Diagram(s)sequenceDiagram
participant Cron as Cron Endpoint
participant ImportPartners as importPartners
participant Redis as Redis Cache
participant Queue as Task Queue
participant ImportCoupons as importAffiliateCoupons
participant RewardfulAPI as Rewardful API
Cron->>ImportPartners: Partner import (action: import-partners)
ImportPartners->>ImportPartners: Create partners from affiliates
ImportPartners->>Redis: HSET affiliate→partner mapping
ImportPartners->>Queue: Queue next task
Queue->>ImportCoupons: action: import-affiliate-coupons
ImportCoupons->>Redis: Fetch affiliate→partner mappings (HMGET)
ImportCoupons->>RewardfulAPI: listAffiliateCoupons(page)
RewardfulAPI-->>ImportCoupons: Return coupons batch
loop For each matched affiliate
ImportCoupons->>ImportCoupons: Collect active coupons
ImportCoupons->>ImportCoupons: Build link objects with metadata
end
ImportCoupons->>ImportCoupons: bulkCreateLinks(links)
alt More pages available
ImportCoupons->>Queue: Queue next batch
else No more data
ImportCoupons->>Redis: Delete cache entry
ImportCoupons->>Queue: Queue import-customers
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The changes span multiple interconnected files with logic modifications across the import pipeline. Key complexity points include: new paginated import function with Redis-based partner mapping, modifications to existing import functions' control flow and return shapes, cross-file data dependencies (affiliate IDs persisted in Redis and consumed by coupon import), and updates to the referral data model to support both link and coupon-based imports. Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/rewardful/import-partners.ts (1)
143-225
: Declare explicit return type forcreatePartnerAndLinks
.The function has multiple early returns (lines 198, 203) that return
undefined
, but the return type is not explicitly declared. This makes the code harder to reason about and the filtering on line 112 necessary but implicit.Add an explicit return type:
async function createPartnerAndLinks({ program, affiliate, userId, defaultGroupAttributes, partnerGroupDefaultLinkId, }: { program: Program; affiliate: RewardfulAffiliate; userId: string; defaultGroupAttributes: { groupId: string; saleRewardId: string | null; leadRewardId: string | null; clickRewardId: string | null; discountId: string | null; }; partnerGroupDefaultLinkId?: string | null; -}) { +}): Promise<{ rewardfulAffiliateId: string; dubPartnerId: string } | undefined> {This makes the contract explicit and clarifies why filtering is needed on line 112.
🧹 Nitpick comments (2)
apps/web/lib/rewardful/import-affiliate-coupons.ts (2)
94-96
: Add error handling and observability.The
bulkCreateLinks
call lacks error handling and result logging, which could make debugging import issues difficult.Consider wrapping in try-catch and logging results:
+ console.log(`Creating ${linksToCreate.length} links for ${Object.keys(filteredPartners).length} partners`); + + try { await bulkCreateLinks({ links: linksToCreate, }); + + console.log(`Successfully created links for page ${currentPage}`); + } catch (error) { + console.error(`Failed to create links for page ${currentPage}:`, error); + throw error; + }
46-55
: Add validation for Redis mapping availability.The code silently proceeds if the Redis key doesn't exist or all values are null. This creates a dependency on
import-partners
having completed successfully. Consider adding validation or logging.Add a check and log when no mappings are found:
const filteredPartners = Object.fromEntries( Object.entries(results ?? {}).filter( ([_, value]) => value !== null && value !== undefined, ), ); + +if (affiliateIds.length > 0 && Object.keys(filteredPartners).length === 0) { + console.warn( + `No partner mappings found in Redis for ${affiliateIds.length} affiliate IDs. ` + + `Ensure import-partners completed successfully for program ${program.id}.` + ); +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/app/(ee)/api/cron/import/rewardful/route.ts
(2 hunks)apps/web/lib/rewardful/api.ts
(2 hunks)apps/web/lib/rewardful/import-affiliate-coupons.ts
(1 hunks)apps/web/lib/rewardful/import-campaigns.ts
(2 hunks)apps/web/lib/rewardful/import-partners.ts
(5 hunks)apps/web/lib/rewardful/schemas.ts
(1 hunks)apps/web/lib/rewardful/types.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/web/app/(ee)/api/cron/import/rewardful/route.ts (1)
apps/web/lib/rewardful/import-affiliate-coupons.ts (1)
importAffiliateCoupons
(8-114)
apps/web/lib/rewardful/api.ts (2)
apps/web/lib/firstpromoter/importer.ts (1)
PAGE_LIMIT
(6-6)apps/web/lib/rewardful/types.ts (1)
RewardfulAffiliateCoupon
(103-110)
apps/web/lib/rewardful/import-affiliate-coupons.ts (5)
apps/web/lib/rewardful/types.ts (1)
RewardfulImportPayload
(112-114)apps/web/lib/rewardful/api.ts (1)
RewardfulApi
(21-109)apps/web/lib/firstpromoter/importer.ts (1)
MAX_BATCHES
(7-7)apps/web/lib/upstash/redis.ts (1)
redis
(4-7)apps/web/lib/api/links/bulk-create-links.ts (1)
bulkCreateLinks
(18-236)
apps/web/lib/rewardful/import-partners.ts (1)
apps/web/lib/upstash/redis.ts (1)
redis
(4-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
apps/web/lib/rewardful/import-campaigns.ts (2)
67-69
: Good observability enhancement.The timestamp-based logging helps distinguish between newly created groups and upserted existing ones, which is useful for debugging the import process.
95-97
: Good observability enhancement.Logging reward creation details aids in tracing the import workflow.
apps/web/app/(ee)/api/cron/import/rewardful/route.ts (1)
3-3
: LGTM!The new import action handler follows the established pattern and integrates cleanly into the existing switch-case structure.
Also applies to: 27-29
apps/web/lib/rewardful/schemas.ts (1)
6-6
: LGTM!The new enum member correctly extends the import workflow steps.
apps/web/lib/rewardful/types.ts (1)
103-110
: LGTM!The interface definition is clear and captures the necessary coupon attributes.
Note: The
archived
field suggests some coupons may be inactive. Verify whether the import logic inimport-affiliate-coupons.ts
should filter out archived coupons before creating links.apps/web/lib/rewardful/api.ts (1)
4-4
: LGTM!The new
listAffiliateCoupons
method follows the established pattern for paginated API calls and is consistent with other list methods in this class.Also applies to: 98-108
apps/web/lib/rewardful/import-affiliate-coupons.ts (1)
33-40
: Consider filtering archived coupons.The
RewardfulAffiliateCoupon
interface includes anarchived
field, but the import logic doesn't check it. Creating links for archived coupons may not be the intended behavior.Should archived coupons be filtered out before creating links? For example:
const affiliateCoupons = await rewardfulApi.listAffiliateCoupons({ page: currentPage, }); const activeCoupons = affiliateCoupons.filter(coupon => !coupon.archived); if (activeCoupons.length === 0) { hasMore = false; break; }apps/web/lib/rewardful/import-partners.ts (2)
108-115
: Good Redis integration for partner mapping.The Redis hash storage correctly captures the affiliate-to-partner ID mappings needed by the coupon import step, and appropriately filters out undefined results from failed partner creations.
133-133
: LGTM! Updated action correctly sequences the import workflow.Changing from
"import-customers"
to"import-affiliate-coupons"
properly inserts the new coupon import step into the workflow chain.
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: 5
🧹 Nitpick comments (1)
apps/web/lib/rewardful/import-affiliate-coupons.ts (1)
8-114
: Add import error logging for consistency.Other import functions (e.g.,
importPartners
) log errors for entities that aren't imported. This function silently skips coupons for affiliates not found in Redis without logging, making debugging difficult.Add error logging similar to
import-partners.ts
:+ const notImportedCoupons: typeof affiliateCoupons = []; + if (Object.keys(filteredPartners).length > 0) { const linksToCreate: any[] = []; for (const [affiliateId, partnerId] of Object.entries(filteredPartners)) { const coupons = affiliateIdToCouponsMap[affiliateId]; if (!coupons) { continue; } linksToCreate.push( ...coupons.map((coupon) => ({ domain: program.domain, key: coupon.token.toLowerCase(), url: program.url, trackConversion: true, programId, partnerId, folderId: program.defaultFolderId, userId, projectId: program.workspaceId, })), ); } + // Track coupons without matching partners + for (const [affiliateId, coupons] of Object.entries( + affiliateIdToCouponsMap, + )) { + if (!filteredPartners[affiliateId]) { + notImportedCoupons.push(...coupons); + } + } + await bulkCreateLinks({ links: linksToCreate, }); } + + if (notImportedCoupons.length > 0) { + await logImportError( + notImportedCoupons.map((coupon) => ({ + workspace_id: program.workspaceId, + import_id: payload.importId, + source: "rewardful", + entity: "coupon", + entity_id: coupon.id, + code: "AFFILIATE_NOT_FOUND", + message: `Coupon ${coupon.token} not imported because affiliate ${coupon.affiliate_id} was not found in partner mappings.`, + })), + ); + }Don't forget to add the import at the top:
import { logImportError } from "../tinybird/log-import-error";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/app/(ee)/api/cron/import/rewardful/route.ts
(2 hunks)apps/web/lib/rewardful/api.ts
(2 hunks)apps/web/lib/rewardful/import-affiliate-coupons.ts
(1 hunks)apps/web/lib/rewardful/import-campaigns.ts
(2 hunks)apps/web/lib/rewardful/import-partners.ts
(5 hunks)apps/web/lib/rewardful/schemas.ts
(1 hunks)apps/web/lib/rewardful/types.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/web/lib/rewardful/import-partners.ts (1)
apps/web/lib/upstash/redis.ts (1)
redis
(4-7)
apps/web/lib/rewardful/import-affiliate-coupons.ts (5)
apps/web/lib/rewardful/types.ts (1)
RewardfulImportPayload
(112-114)apps/web/lib/rewardful/api.ts (1)
RewardfulApi
(21-109)apps/web/lib/firstpromoter/importer.ts (1)
MAX_BATCHES
(7-7)apps/web/lib/upstash/redis.ts (1)
redis
(4-7)apps/web/lib/api/links/bulk-create-links.ts (1)
bulkCreateLinks
(18-236)
apps/web/lib/rewardful/api.ts (2)
apps/web/lib/firstpromoter/importer.ts (1)
PAGE_LIMIT
(6-6)apps/web/lib/rewardful/types.ts (1)
RewardfulAffiliateCoupon
(103-110)
apps/web/app/(ee)/api/cron/import/rewardful/route.ts (1)
apps/web/lib/rewardful/import-affiliate-coupons.ts (1)
importAffiliateCoupons
(8-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
apps/web/lib/rewardful/schemas.ts (1)
6-6
: LGTM!The new enum member correctly extends the import workflow steps.
apps/web/lib/rewardful/import-campaigns.ts (2)
62-69
: LGTM!The timing log provides useful observability for debugging upsert behavior.
95-97
: LGTM!Logging reward creation details aids in troubleshooting the import flow.
apps/web/lib/rewardful/import-partners.ts (3)
76-115
: LGTM! Redis mapping enables coupon import.The affiliate-to-partner mapping is correctly persisted to Redis and will be consumed by the subsequent coupon import step. The filter on line 112 properly handles undefined returns from
createPartnerAndLinks
.
133-133
: LGTM!Correctly chains to the new "import-affiliate-coupons" action.
221-224
: LGTM!Returning both IDs enables the caller to populate the Redis mapping.
apps/web/app/(ee)/api/cron/import/rewardful/route.ts (1)
3-3
: LGTM!The route handler correctly integrates the new import action following the established pattern.
Also applies to: 27-29
apps/web/lib/rewardful/api.ts (1)
4-4
: LGTM!The new API method follows the established pattern and correctly uses pagination parameters.
Also applies to: 98-108
apps/web/lib/rewardful/types.ts (1)
103-110
: LGTM!The interface correctly models the Rewardful affiliate coupon structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/lib/rewardful/api.ts
(2 hunks)apps/web/lib/rewardful/import-customers.ts
(2 hunks)apps/web/lib/rewardful/import-partners.ts
(5 hunks)apps/web/lib/rewardful/types.ts
(2 hunks)apps/web/scripts/migrations/migrate-sales.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/rewardful/types.ts
- apps/web/lib/rewardful/import-partners.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/rewardful/import-customers.ts (1)
apps/web/lib/tinybird/log-import-error.ts (1)
logImportError
(16-19)
apps/web/lib/rewardful/api.ts (2)
apps/web/lib/firstpromoter/importer.ts (1)
PAGE_LIMIT
(6-6)apps/web/lib/rewardful/types.ts (1)
RewardfulCoupon
(104-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (3)
apps/web/lib/rewardful/api.ts (1)
98-108
: LGTM! Method follows established patterns.The
listAffiliateCoupons
method is correctly implemented and consistent with the existing list methods in the class.apps/web/lib/rewardful/import-customers.ts (2)
102-110
: LGTM! Validation correctly handles both links and coupons.The expanded validation logic appropriately allows referrals to be processed when either a link or coupon is present.
119-136
: LGTM! Correctly uses the derived shortLinkToken.The link lookup and error messaging correctly use
shortLinkToken
, which works for both link-based and coupon-based referrals.
const shortLinkToken = referral.link?.token || referral.coupon?.token; | ||
|
||
if (!shortLinkToken) { | ||
console.error(`Short link token not found for referral ${referralId}.`); | ||
return; | ||
} |
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.
Use structured error logging for consistency.
While the validation logic is sound, the error handling here uses console.error
instead of logImportError
, which is inconsistent with the error-handling pattern used elsewhere in this function (e.g., lines 103-107, 129-133, 142-146).
Apply this diff to maintain consistent error logging:
const shortLinkToken = referral.link?.token || referral.coupon?.token;
if (!shortLinkToken) {
- console.error(`Short link token not found for referral ${referralId}.`);
+ await logImportError({
+ ...commonImportLogInputs,
+ code: "LINK_TOKEN_NOT_FOUND",
+ message: `Link token not found for referral ${referralId}.`,
+ });
return;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const shortLinkToken = referral.link?.token || referral.coupon?.token; | |
if (!shortLinkToken) { | |
console.error(`Short link token not found for referral ${referralId}.`); | |
return; | |
} | |
const shortLinkToken = referral.link?.token || referral.coupon?.token; | |
if (!shortLinkToken) { | |
await logImportError({ | |
...commonImportLogInputs, | |
code: "LINK_TOKEN_NOT_FOUND", | |
message: `Link token not found for referral ${referralId}.`, | |
}); | |
return; | |
} |
🤖 Prompt for AI Agents
In apps/web/lib/rewardful/import-customers.ts around lines 112 to 117, replace
the console.error usage with the module's structured logger to match the rest of
the function: call logImportError(referralId, `Short link token not found for
referral ${referralId}.`) (or the existing logImportError signature used
elsewhere) and then return, so the error is recorded consistently with other
failures in this file.
async function main() { | ||
// @ts-ignore (old sales table) | ||
const sales = await prisma.sale.findMany({ | ||
select: { | ||
id: true, | ||
programId: true, | ||
partnerId: true, | ||
linkId: true, | ||
payoutId: true, | ||
invoiceId: true, | ||
customerId: true, | ||
eventId: true, | ||
amount: true, | ||
earnings: true, | ||
currency: true, | ||
status: true, | ||
createdAt: true, | ||
updatedAt: true, | ||
const program = await prisma.program.findUniqueOrThrow({ | ||
where: { | ||
id: "prog_1K2J9DRWPPJ2F1RX53N92TSGA", | ||
}, | ||
take: 1000, | ||
skip: 2500, | ||
}); | ||
|
||
if (!sales.length) { | ||
console.log("No sales found."); | ||
return; | ||
} | ||
|
||
await prisma.commission.createMany({ | ||
data: sales.map((sale) => ({ | ||
...sale, | ||
id: createId({ prefix: "cm_" }), | ||
type: EventType.sale, | ||
quantity: 1, | ||
})), | ||
skipDuplicates: true, | ||
const link = await prisma.link.findUnique({ | ||
where: { | ||
domain_key: { | ||
domain: program.domain!, | ||
key: "BESTPIC", | ||
}, | ||
}, | ||
}); | ||
|
||
console.log(`Migrated ${sales.length} sales.`); | ||
console.log(link) | ||
} |
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.
🧩 Analysis chain
Debug code should not be committed to the repository.
This migration script has been replaced with what appears to be test or debug code that:
- Uses hardcoded program ID and link key
- Only fetches and logs a single link
- Contains no actual migration logic
This should either be reverted to its original state, removed from the PR, or explained if there's a specific reason for this change.
Please clarify the intent of this change. If this was meant for local testing, it should not be included in the PR.
I'll investigate the migration script to verify the review comment. Let me gather information about the file's history and context.
---
🏁 Script executed:
```shell
#!/bin/bash
# Check git diff for the migration file to see what changed
git diff HEAD -- apps/web/scripts/migrations/migrate-sales.ts || true
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check git log to see recent changes to this file
git log --oneline -n 20 -- apps/web/scripts/migrations/migrate-sales.ts || true
Length of output: 106
🏁 Script executed:
#!/bin/bash
# Find other migration scripts to compare pattern
fd -t f 'migrate-.*\.ts$' apps/web/scripts/migrations/ || fd -t f '.*migration.*\.ts$' apps/web/scripts/
Length of output: 759
🏁 Script executed:
#!/bin/bash
# Show the complete current content of the migration file
cat apps/web/scripts/migrations/migrate-sales.ts || echo "File not found"
Length of output: 507
🏁 Script executed:
#!/bin/bash
# Check for any test or commented-out code patterns in the file
grep -n "test\|TODO\|FIXME\|debug\|temporary" apps/web/scripts/migrations/migrate-sales.ts -i || echo "No markers found"
Length of output: 16
I need to compare with other migration files to understand the expected pattern and clarify the file's status in this PR.
---
🏁 Script executed:
```shell
#!/bin/bash
# Check git status to see if file is new, modified, or untracked in current branch
git status -- apps/web/scripts/migrations/migrate-sales.ts
Length of output: 63
🏁 Script executed:
#!/bin/bash
# Examine 2 other migration files to understand expected pattern
echo "=== migrate-rewards.ts ===" && head -50 apps/web/scripts/migrations/migrate-rewards.ts && echo -e "\n=== migrate-discounts.ts ===" && head -50 apps/web/scripts/migrations/migrate-discounts.ts
Length of output: 2296
🏁 Script executed:
#!/bin/bash
# Check if migrate-sales.ts exists in HEAD (to verify if it's new or modified)
git show HEAD:apps/web/scripts/migrations/migrate-sales.ts 2>/dev/null | head -40 || echo "File does not exist in HEAD"
Length of output: 507
I need to understand the commit history context better to determine if this is a new issue in this PR or a pre-existing problem.
---
🏁 Script executed:
```shell
#!/bin/bash
# Show the commit that introduced/modified this file
git log -p --follow -n 5 -- apps/web/scripts/migrations/migrate-sales.ts | head -100
Length of output: 59
🏁 Script executed:
#!/bin/bash
# Check what branch we're on and compare to main
git log --oneline -n 1 && echo "---" && git log --oneline origin/main -n 1 2>/dev/null || echo "Cannot access origin/main"
Length of output: 114
🏁 Script executed:
#!/bin/bash
# Show commits in current branch that aren't in main
git log --oneline origin/main..HEAD -- apps/web/scripts/migrations/migrate-sales.ts 2>/dev/null || echo "Cannot compare or no changes in this file since main"
Length of output: 106
Remove or replace debug code in migration script.
This file is part of the current PR branch and contains incomplete code. Unlike other migration scripts in the repository (e.g., migrate-rewards.ts
, migrate-discounts.ts
), this file:
- Lacks explanatory comments documenting the migration purpose
- Contains hardcoded IDs instead of querying dynamically
- Only fetches and logs data with no transformation or migration operations
- Performs no actual data updates, deletes, or transformations
Replace with actual migration logic or remove the file if no migration is needed.
🤖 Prompt for AI Agents
In apps/web/scripts/migrations/migrate-sales.ts around lines 6 to 23, the script
contains leftover debug code: hardcoded program ID, a single read of a link, and
a console.log with no migration, comments, or dynamic querying; either implement
the intended migration or remove the file. To fix, replace the debug block with
a properly documented migration function (add a descriptive header comment
explaining purpose and safe rollback notes), query programs/links dynamically
(avoid hardcoded IDs—use criteria or iterate over applicable rows), perform the
necessary update/delete/transform operations using prisma transactions, handle
and log errors, and include a dry-run or confirmation step; if no migration is
required, delete this file from the branch to avoid shipping incomplete code.
Summary by CodeRabbit
New Features
Improvements