Skip to content

Commit 1909708

Browse files
authored
Improve merge-partners flow (dubinc#3584)
1 parent d59de60 commit 1909708

2 files changed

Lines changed: 77 additions & 54 deletions

File tree

  • apps/web/app/(ee)/api

apps/web/app/(ee)/api/admin/delete-partner-account/route.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { withAdmin } from "@/lib/auth";
2+
import { conn } from "@/lib/planetscale";
23
import { stripe } from "@/lib/stripe";
34
import { recordLink } from "@/lib/tinybird";
45
import { prisma } from "@dub/prisma";
@@ -115,14 +116,22 @@ export const POST = withAdmin(async ({ req }) => {
115116
);
116117
}
117118
}
119+
120+
await prisma.programEnrollment.deleteMany({
121+
where: {
122+
partnerId: partner.id,
123+
programId: {
124+
in: partner.programs.map(({ program }) => program.id),
125+
},
126+
},
127+
});
128+
console.log(
129+
`Deleted ${partner.programs.length} program enrollments for partner ${partner.email} (${partner.id})`,
130+
);
118131
}
119132

120-
const deletedPartner = await prisma.partner.delete({
121-
where: {
122-
id: partner.id,
123-
},
124-
});
125-
console.log("Deleted partner", deletedPartner);
133+
await conn.execute(`DELETE FROM Partner WHERE id = ?`, [partner.id]);
134+
console.log(`Deleted partner ${partner.email} (${partner.id})`);
126135
}
127136

128137
return NextResponse.json({ success: true });

apps/web/app/(ee)/api/cron/partners/merge-accounts/route.ts

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { includeProgramEnrollment } from "@/lib/api/links/include-program-enroll
55
import { includeTags } from "@/lib/api/links/include-tags";
66
import { syncTotalCommissions } from "@/lib/api/partners/sync-total-commissions";
77
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
8+
import { conn } from "@/lib/planetscale";
89
import { storage } from "@/lib/storage";
910
import { recordLink } from "@/lib/tinybird";
1011
import { redis } from "@/lib/upstash";
1112
import { sendBatchEmail } from "@dub/email";
1213
import PartnerAccountMerged from "@dub/email/templates/partner-account-merged";
1314
import { prisma } from "@dub/prisma";
14-
import { log, prettyPrint, R2_URL, toCentsNumber } from "@dub/utils";
15+
import { log, prettyPrint, R2_URL } from "@dub/utils";
1516
import * as z from "zod/v4";
1617

1718
export const dynamic = "force-dynamic";
@@ -60,10 +61,12 @@ export async function POST(req: Request) {
6061
select: {
6162
id: true,
6263
email: true,
64+
image: true,
6365
payoutMethodHash: true,
6466
programs: {
6567
select: {
6668
programId: true,
69+
tenantId: true,
6770
},
6871
},
6972
users: {
@@ -99,6 +102,12 @@ export async function POST(req: Request) {
99102
);
100103
}
101104

105+
if (sourceAccount.id === targetAccount.id) {
106+
return new Response(
107+
`Source and target partner accounts must be different. Source account: ${sourceAccount.email} (${sourceAccount.id}), Target account: ${targetAccount.email} (${targetAccount.id})`,
108+
);
109+
}
110+
102111
const {
103112
id: sourcePartnerId,
104113
users: sourcePartnerUsers,
@@ -164,18 +173,20 @@ export async function POST(req: Request) {
164173
`Updated ${updatedLinksRes.count} links, ${updatedCustomersRes.count} customers, ${updatedCommissionsRes.count} commissions, and ${updatedPayoutsRes.count} payouts`,
165174
);
166175

167-
// update notification emails, messages, and partner comments
176+
// update discount codes, notification emails, messages, and partner comments
168177
const [
178+
updatedDiscountCodesRes,
169179
updatedNotificationEmailsRes,
170180
updatedMessagesRes,
171181
updatedPartnerCommentsRes,
172182
] = await Promise.all([
183+
prisma.discountCode.updateMany(updateManyPayload),
173184
prisma.notificationEmail.updateMany(updateManyPayload),
174185
prisma.message.updateMany(updateManyPayload),
175186
prisma.partnerComment.updateMany(updateManyPayload),
176187
]);
177188
console.log(
178-
`Updated ${updatedNotificationEmailsRes.count} notification emails, ${updatedMessagesRes.count} messages, and ${updatedPartnerCommentsRes.count} partner comments`,
189+
`Updated ${updatedDiscountCodesRes.count} discount codes, ${updatedNotificationEmailsRes.count} notification emails, ${updatedMessagesRes.count} messages, and ${updatedPartnerCommentsRes.count} partner comments`,
179190
);
180191

181192
const updatedLinks = await prisma.link.findMany({
@@ -240,6 +251,50 @@ export async function POST(req: Request) {
240251
console.log(prettyPrint(res));
241252
}
242253

254+
const existingEnrollments = sourcePartnerEnrollments.filter(
255+
({ programId }) =>
256+
targetPartnerEnrollments.some(
257+
({ programId: targetProgramId }) => programId === targetProgramId,
258+
),
259+
);
260+
261+
if (existingEnrollments.length > 0) {
262+
for (const sourceEnrollment of existingEnrollments) {
263+
const targetEnrollment = targetPartnerEnrollments.find(
264+
({ programId }) => programId === sourceEnrollment.programId,
265+
);
266+
await prisma.$transaction(async (tx) => {
267+
// delete old source enrollment
268+
await tx.programEnrollment.delete({
269+
where: {
270+
partnerId_programId: {
271+
partnerId: sourcePartnerId,
272+
programId: sourceEnrollment.programId,
273+
},
274+
},
275+
});
276+
277+
// update target enrollment with source enrollment's tenantId if target enrollment does not have a tenantId
278+
if (sourceEnrollment.tenantId && !targetEnrollment?.tenantId) {
279+
await tx.programEnrollment.update({
280+
where: {
281+
partnerId_programId: {
282+
partnerId: targetPartnerId,
283+
programId: sourceEnrollment.programId,
284+
},
285+
},
286+
data: {
287+
tenantId: sourceEnrollment.tenantId,
288+
},
289+
});
290+
}
291+
});
292+
console.log(
293+
`Deleted old source enrollment for program ${sourceEnrollment.programId}.${sourceEnrollment.tenantId ? ` Since there was a tenantId, we updated the target enrollment with the same tenantId: ${sourceEnrollment.tenantId}` : ""}`,
294+
);
295+
}
296+
}
297+
243298
// If source account has rewind, need to delete and recalculate for the target account
244299
if (sourceAccount.partnerRewinds.length > 0) {
245300
const deletedRewinds = await prisma.partnerRewind.deleteMany({
@@ -248,43 +303,6 @@ export async function POST(req: Request) {
248303
},
249304
});
250305
console.log(`Deleted ${deletedRewinds.count} partner rewinds`);
251-
252-
const rewindStats = await prisma.programEnrollment
253-
.aggregate({
254-
where: {
255-
partnerId: targetPartnerId,
256-
},
257-
_sum: {
258-
totalClicks: true,
259-
totalLeads: true,
260-
totalSaleAmount: true,
261-
totalCommissions: true,
262-
},
263-
})
264-
.then((res) => ({
265-
totalClicks: res._sum.totalClicks ?? 0,
266-
totalLeads: res._sum.totalLeads ?? 0,
267-
totalRevenue: toCentsNumber(res._sum.totalSaleAmount ?? 0),
268-
totalEarnings: toCentsNumber(res._sum.totalCommissions ?? 0),
269-
}));
270-
271-
await prisma.partnerRewind.upsert({
272-
where: {
273-
partnerId_year: {
274-
partnerId: targetPartnerId,
275-
year: 2025,
276-
},
277-
},
278-
update: rewindStats,
279-
create: {
280-
partnerId: targetPartnerId,
281-
year: 2025,
282-
...rewindStats,
283-
},
284-
});
285-
console.log(
286-
`Upserted partner rewind for ${targetPartnerId} in 2025: ${prettyPrint(rewindStats)}`,
287-
);
288306
}
289307

290308
// Remove the user if there are no workspaces left
@@ -327,18 +345,14 @@ export async function POST(req: Request) {
327345

328346
try {
329347
// Finally, delete the partner account
330-
const deletedPartner = await prisma.partner.delete({
331-
where: {
332-
id: sourcePartnerId,
333-
},
334-
});
348+
await conn.execute(`DELETE FROM Partner WHERE id = ?`, [sourcePartnerId]);
335349
console.log(
336-
`Deleted partner ${deletedPartner.email} (${deletedPartner.id})`,
350+
`Deleted partner ${sourceAccount.email} (${sourceAccount.id})`,
337351
);
338352

339-
if (deletedPartner.image) {
353+
if (sourceAccount.image) {
340354
await storage.delete({
341-
key: deletedPartner.image.replace(`${R2_URL}/`, ""),
355+
key: sourceAccount.image.replace(`${R2_URL}/`, ""),
342356
});
343357
}
344358
} catch (error) {

0 commit comments

Comments
 (0)