-
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
Changes from all commits
fa82d53
bb861bf
2b5ada9
32ff7e0
3e3adae
a9a4725
6f1b31c
d1924eb
ca76c57
31e2bc2
8e42b2c
cdd99ba
dbd6ba5
d0512a9
d7314e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { prisma } from "@dub/prisma"; | ||
import { bulkCreateLinks } from "../api/links"; | ||
import { redis } from "../upstash"; | ||
import { RewardfulApi } from "./api"; | ||
import { MAX_BATCHES, rewardfulImporter } from "./importer"; | ||
import { RewardfulImportPayload } from "./types"; | ||
|
||
export async function importAffiliateCoupons(payload: RewardfulImportPayload) { | ||
const { programId, userId, page = 1 } = payload; | ||
|
||
const program = await prisma.program.findUniqueOrThrow({ | ||
where: { | ||
id: programId, | ||
}, | ||
select: { | ||
id: true, | ||
workspaceId: true, | ||
domain: true, | ||
url: true, | ||
defaultFolderId: true, | ||
}, | ||
}); | ||
|
||
const { token } = await rewardfulImporter.getCredentials(program.workspaceId); | ||
|
||
const rewardfulApi = new RewardfulApi({ token }); | ||
|
||
let currentPage = page; | ||
let hasMore = true; | ||
let processedBatches = 0; | ||
|
||
while (hasMore && processedBatches < MAX_BATCHES) { | ||
const affiliateCoupons = await rewardfulApi.listAffiliateCoupons({ | ||
page: currentPage, | ||
}); | ||
|
||
if (affiliateCoupons.length === 0) { | ||
hasMore = false; | ||
break; | ||
} | ||
devkiran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const affiliateIds = affiliateCoupons.map( | ||
(affiliateCoupon) => affiliateCoupon.affiliate_id, | ||
); | ||
|
||
const results = await redis.hmget<Record<string, string>>( | ||
`rewardful:affiliates:${program.id}`, | ||
...affiliateIds, | ||
); | ||
|
||
const filteredPartners = Object.fromEntries( | ||
Object.entries(results ?? {}).filter( | ||
([_, value]) => value !== null && value !== undefined, | ||
), | ||
); | ||
|
||
const affiliateIdToCouponsMap = affiliateCoupons.reduce( | ||
(acc, coupon) => { | ||
if (!acc[coupon.affiliate_id]) { | ||
acc[coupon.affiliate_id] = []; | ||
} | ||
|
||
acc[coupon.affiliate_id].push(coupon); | ||
return acc; | ||
}, | ||
{} as Record<string, typeof affiliateCoupons>, | ||
); | ||
|
||
if (Object.keys(filteredPartners).length > 0) { | ||
const linksToCreate: any[] = []; | ||
devkiran marked this conversation as resolved.
Show resolved
Hide resolved
devkiran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
for (const [affiliateId, partnerId] of Object.entries(filteredPartners)) { | ||
const coupons = affiliateIdToCouponsMap[affiliateId]; | ||
|
||
if (!coupons) { | ||
continue; | ||
} | ||
|
||
const activeCoupons = affiliateCoupons.filter( | ||
(coupon) => !coupon.archived, | ||
); | ||
|
||
if (activeCoupons.length === 0) { | ||
continue; | ||
} | ||
|
||
linksToCreate.push( | ||
...activeCoupons.map((coupon) => ({ | ||
domain: program.domain, | ||
key: coupon.token, | ||
url: program.url, | ||
trackConversion: true, | ||
programId, | ||
partnerId, | ||
folderId: program.defaultFolderId, | ||
userId, | ||
projectId: program.workspaceId, | ||
})), | ||
); | ||
} | ||
|
||
await bulkCreateLinks({ | ||
links: linksToCreate, | ||
}); | ||
} | ||
|
||
currentPage++; | ||
processedBatches++; | ||
} | ||
|
||
if (!hasMore) { | ||
await redis.del(`rewardful:affiliates:${program.id}`); | ||
} | ||
devkiran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const action = hasMore ? "import-affiliate-coupons" : "import-customers"; | ||
|
||
await rewardfulImporter.queue({ | ||
...payload, | ||
action, | ||
page: hasMore ? currentPage : undefined, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -99,21 +99,28 @@ async function createCustomer({ | |||||||||||||||||||||||||||||||||
entity_id: referralId, | ||||||||||||||||||||||||||||||||||
} as const; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (!referral.link) { | ||||||||||||||||||||||||||||||||||
if (!referral.link && !referral.coupon) { | ||||||||||||||||||||||||||||||||||
await logImportError({ | ||||||||||||||||||||||||||||||||||
...commonImportLogInputs, | ||||||||||||||||||||||||||||||||||
code: "LINK_NOT_FOUND", | ||||||||||||||||||||||||||||||||||
message: `Link not found for referral ${referralId} (could've been a coupon-based referral).`, | ||||||||||||||||||||||||||||||||||
message: `Link or coupon not found for referral ${referralId}.`, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const shortLinkToken = referral.link?.token || referral.coupon?.token; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (!shortLinkToken) { | ||||||||||||||||||||||||||||||||||
console.error(`Short link token not found for referral ${referralId}.`); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Comment on lines
+112
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const link = await prisma.link.findUnique({ | ||||||||||||||||||||||||||||||||||
where: { | ||||||||||||||||||||||||||||||||||
domain_key: { | ||||||||||||||||||||||||||||||||||
domain: program.domain!, | ||||||||||||||||||||||||||||||||||
key: referral.link.token, | ||||||||||||||||||||||||||||||||||
key: shortLinkToken, | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
@@ -122,7 +129,7 @@ async function createCustomer({ | |||||||||||||||||||||||||||||||||
await logImportError({ | ||||||||||||||||||||||||||||||||||
...commonImportLogInputs, | ||||||||||||||||||||||||||||||||||
code: "LINK_NOT_FOUND", | ||||||||||||||||||||||||||||||||||
message: `Link not found for referral ${referralId} (token: ${referral.link.token}).`, | ||||||||||||||||||||||||||||||||||
message: `Link not found for referral ${referralId} (token: ${shortLinkToken}).`, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.