From d06a735d3e276d00fe36d275da69acb1e2d69c63 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Wed, 25 Mar 2026 15:16:40 +0530 Subject: [PATCH 1/4] Fix race condition in partner upsert causing unique constraint violation When concurrent requests hit /api/tokens/embed/referrals with the same partner email, the nested ProgramEnrollment create inside partner.upsert could violate the unique(partnerId, programId) constraint. Handle P2002 errors by falling back to returning the existing enrollment. --- .../api/partners/create-and-enroll-partner.ts | 84 ++++++++++++++----- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/apps/web/lib/api/partners/create-and-enroll-partner.ts b/apps/web/lib/api/partners/create-and-enroll-partner.ts index 9a208ddd5cf..d21df1e258a 100644 --- a/apps/web/lib/api/partners/create-and-enroll-partner.ts +++ b/apps/web/lib/api/partners/create-and-enroll-partner.ts @@ -1,5 +1,3 @@ -"use server"; - import { createId } from "@/lib/api/create-id"; import { polyfillSocialMediaFields } from "@/lib/social-utils"; import { isStored, storage } from "@/lib/storage"; @@ -160,29 +158,73 @@ export const createAndEnrollPartner = async ({ }, }; - const upsertedPartner = await prisma.partner.upsert({ - where: { - email: partner.email, - }, - update: payload, - create: { - ...payload, - id: createId({ prefix: "pn_" }), - name: partner.name || partner.email, - email: partner.email, - image: partner.image && !isStored(partner.image) ? null : partner.image, - country: partner.country, - description: partner.description, - }, + let upsertedPartner: Prisma.PartnerGetPayload<{ include: { - platforms: true, - programs: { + platforms: true; + programs: true; + }; + }>; + + try { + upsertedPartner = await prisma.partner.upsert({ + where: { + email: partner.email, + }, + update: payload, + create: { + ...payload, + id: createId({ prefix: "pn_" }), + name: partner.name || partner.email, + email: partner.email, + image: partner.image && !isStored(partner.image) ? null : partner.image, + country: partner.country, + description: partner.description, + }, + include: { + platforms: true, + programs: { + where: { + programId: program.id, + }, + }, + }, + }); + } catch (error: any) { + // Handle race condition: another request created the enrollment between + // our check and the upsert, violating the unique constraint + if (error.code === "P2002") { + const existingEnrollment = await prisma.programEnrollment.findFirst({ where: { programId: program.id, + partner: { + email: partner.email, + }, }, - }, - }, - }); + include: { + partner: { + include: { + platforms: true, + }, + }, + links: true, + }, + }); + + if (existingEnrollment) { + return EnrolledPartnerSchema.parse({ + ...existingEnrollment.partner, + ...existingEnrollment, + id: existingEnrollment.partner.id, + links: existingEnrollment.links, + ...polyfillSocialMediaFields(existingEnrollment.partner.platforms), + }); + } + + throw error; + } + + throw error; + } // Create the partner links based on group defaults const links = await createPartnerDefaultLinks({ From 9a47a558042210c73e9722ec7aa818b6913e66d9 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Wed, 25 Mar 2026 18:48:30 +0530 Subject: [PATCH 2/4] Update create-and-enroll-partner.ts --- .../api/partners/create-and-enroll-partner.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/web/lib/api/partners/create-and-enroll-partner.ts b/apps/web/lib/api/partners/create-and-enroll-partner.ts index d21df1e258a..0e075da1c2d 100644 --- a/apps/web/lib/api/partners/create-and-enroll-partner.ts +++ b/apps/web/lib/api/partners/create-and-enroll-partner.ts @@ -211,6 +211,43 @@ export const createAndEnrollPartner = async ({ }); if (existingEnrollment) { + if ( + partner.tenantId && + partner.tenantId !== existingEnrollment.tenantId + ) { + await throwIfExistingTenantEnrollmentExists({ + tenantId: partner.tenantId, + programId: program.id, + }); + + const updatedEnrollment = await prisma.programEnrollment.update({ + where: { + id: existingEnrollment.id, + }, + data: { + tenantId: partner.tenantId, + }, + include: { + partner: { + include: { + platforms: true, + }, + }, + links: true, + }, + }); + + return EnrolledPartnerSchema.parse({ + ...updatedEnrollment.partner, + ...updatedEnrollment, + id: updatedEnrollment.partner.id, + links: updatedEnrollment.links, + ...polyfillSocialMediaFields( + updatedEnrollment.partner.platforms, + ), + }); + } + return EnrolledPartnerSchema.parse({ ...existingEnrollment.partner, ...existingEnrollment, From c2ceb27f7619943de178cd15d4165420ac835b8e Mon Sep 17 00:00:00 2001 From: Kiran K Date: Wed, 25 Mar 2026 19:24:44 +0530 Subject: [PATCH 3/4] Update playwright.yaml --- .github/workflows/playwright.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml index 9b1c941c9b5..ca9d2d831b8 100644 --- a/.github/workflows/playwright.yaml +++ b/.github/workflows/playwright.yaml @@ -29,7 +29,7 @@ jobs: NEXTAUTH_URL: "http://partners.localhost:8888" NEXT_PUBLIC_APP_NAME: "Dub" - NEXT_PUBLIC_APP_DOMAIN: "dub.co" + NEXT_PUBLIC_APP_DOMAIN: "localhost:8888" NEXT_PUBLIC_APP_SHORT_DOMAIN: "dub.sh" E2E_PARTNER_EMAIL: "partner1@dub-internal-test.com" From 0ec6bbc2e221b06cdfaca1ed1d581154821f662f Mon Sep 17 00:00:00 2001 From: Kiran K Date: Wed, 25 Mar 2026 19:25:20 +0530 Subject: [PATCH 4/4] Revert "Update playwright.yaml" This reverts commit c2ceb27f7619943de178cd15d4165420ac835b8e. --- .github/workflows/playwright.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml index ca9d2d831b8..9b1c941c9b5 100644 --- a/.github/workflows/playwright.yaml +++ b/.github/workflows/playwright.yaml @@ -29,7 +29,7 @@ jobs: NEXTAUTH_URL: "http://partners.localhost:8888" NEXT_PUBLIC_APP_NAME: "Dub" - NEXT_PUBLIC_APP_DOMAIN: "localhost:8888" + NEXT_PUBLIC_APP_DOMAIN: "dub.co" NEXT_PUBLIC_APP_SHORT_DOMAIN: "dub.sh" E2E_PARTNER_EMAIL: "partner1@dub-internal-test.com"