-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Improve Rewardful importer to support multi-campaign import #2932
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import { RESOURCE_COLORS } from "@/ui/colors"; | ||
| import { prisma } from "@dub/prisma"; | ||
| import { EventType, RewardStructure } from "@dub/prisma/client"; | ||
| import { randomValue } from "@dub/utils"; | ||
| import { differenceInSeconds } from "date-fns"; | ||
| import { createId } from "../api/create-id"; | ||
| import { RewardfulApi } from "./api"; | ||
| import { rewardfulImporter } from "./importer"; | ||
| import { RewardfulImportPayload } from "./types"; | ||
|
|
||
| export async function importCampaigns(payload: RewardfulImportPayload) { | ||
| const { programId, campaignIds } = payload; | ||
|
|
||
| const { workspaceId } = await prisma.program.findUniqueOrThrow({ | ||
| where: { | ||
| id: programId, | ||
| }, | ||
| }); | ||
|
|
||
| const { token } = await rewardfulImporter.getCredentials(workspaceId); | ||
|
|
||
| const rewardfulApi = new RewardfulApi({ token }); | ||
|
|
||
| const campaigns = await rewardfulApi.listCampaigns(); | ||
| const campaignsToImport = campaigns.filter((campaign) => | ||
| campaignIds.includes(campaign.id), | ||
| ); | ||
|
|
||
| for (const campaign of campaignsToImport) { | ||
| const { | ||
| id: campaignId, | ||
| commission_amount_cents, | ||
| minimum_payout_cents, | ||
| commission_percent, | ||
| max_commission_period_months, | ||
| days_until_commissions_are_due, | ||
| reward_type, | ||
| } = campaign; | ||
|
|
||
| const groupSlug = `rewardful-${campaignId}`; | ||
| const createdGroup = await prisma.partnerGroup.upsert({ | ||
| where: { | ||
| programId_slug: { | ||
| programId, | ||
| slug: groupSlug, | ||
| }, | ||
| }, | ||
| update: {}, | ||
| create: { | ||
| id: createId({ prefix: "grp_" }), | ||
| programId, | ||
| name: `(Rewardful) ${campaign.name}`, | ||
| slug: groupSlug, | ||
| color: randomValue(RESOURCE_COLORS), | ||
| }, | ||
| }); | ||
|
|
||
| console.log( | ||
| `Upserted group ${createdGroup.name} (${createdGroup.id}) matching Rewardful campaign ${campaign.name} (${campaignId}).`, | ||
| ); | ||
|
|
||
| const createdSecondsAgo = differenceInSeconds( | ||
| new Date(), | ||
| createdGroup.createdAt, | ||
| ); | ||
| console.log( | ||
| `This group was created ${createdSecondsAgo} seconds ago (most likely ${createdSecondsAgo < 10 ? "created" : "upserted"})`, | ||
| ); | ||
|
|
||
| if (!createdGroup.saleRewardId) { | ||
| const createdReward = await prisma.reward.create({ | ||
| data: { | ||
| id: createId({ prefix: "rw_" }), | ||
| programId, | ||
| // connect the reward to the group | ||
| salePartnerGroup: { | ||
| connect: { | ||
| id: createdGroup.id, | ||
| }, | ||
| }, | ||
| event: EventType.sale, | ||
| maxDuration: max_commission_period_months, | ||
| type: | ||
| reward_type === "amount" | ||
| ? RewardStructure.flat | ||
| : RewardStructure.percentage, | ||
| amount: | ||
| reward_type === "amount" | ||
| ? commission_amount_cents | ||
| : commission_percent, | ||
| }, | ||
| }); | ||
| console.log( | ||
| `Since group was newly created, also created reward ${createdReward.id} with amount ${createdReward.amount} and type ${createdReward.type}`, | ||
| ); | ||
| } | ||
|
Comment on lines
+70
to
+96
Contributor
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. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainVerify null handling for reward amounts. Lines 88-90 assign Run the following script to check the Rewardful API response schema and Prisma schema: 🏁 Script executed: #!/bin/bash
# Description: Verify nullability of commission fields in types and schema
# Check Rewardful type definitions
rg -nP --type=ts -A5 'interface.*RewardfulCampaign'
# Check if these fields are nullable
rg -nP --type=ts 'commission_amount_cents|commission_percent'
# Check Prisma schema for Reward.amount nullability
fd -e prisma -x cat {} | grep -A10 "model Reward"Length of output: 1824 Add runtime checks for commission values |
||
|
|
||
| if (campaign.default) { | ||
| await prisma.program.update({ | ||
| where: { | ||
| id: programId, | ||
| }, | ||
| data: { | ||
| minPayoutAmount: minimum_payout_cents, | ||
| holdingPeriodDays: days_until_commissions_are_due, | ||
| }, | ||
| }); | ||
| console.log( | ||
| `Updated program ${programId} with min payout amount ${minimum_payout_cents} and holding period days ${days_until_commissions_are_due}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| return await rewardfulImporter.queue({ | ||
| ...payload, | ||
| action: "import-partners", | ||
| }); | ||
| } | ||
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.
🛠️ Refactor suggestion | 🟠 Major
Harden action input: enforce at least one campaign ID
startRewardfulImportActionused to require a campaign viaparsedInput.campaignId. With the array form, we should keep that invariant server-side by adding.min(1)to the schema. That way any misuse (tests, future callers, or UI regressions) is caught before queuing an import job.🤖 Prompt for AI Agents