From c9aef38ccfb6696b53117217c1298c964ba17a47 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 06:04:15 +0000 Subject: [PATCH 1/2] fix: harden seed script org settings upsert and P2002 error handling Co-Authored-By: romitgabani1 --- .../lib/utils/isAdmin.integration-test.ts | 53 +-------------- ...eScopedAccessibleUsers.integration-test.ts | 28 +------- scripts/seed.ts | 68 ++++++++++--------- 3 files changed, 38 insertions(+), 111 deletions(-) diff --git a/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts b/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts index f8485605cb3252..e89d447ffb3f87 100644 --- a/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts +++ b/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts @@ -1,10 +1,8 @@ +import prisma from "@calcom/prisma"; import type { Request, Response } from "express"; import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; -import { describe, it, expect, beforeAll } from "vitest"; - -import prisma from "@calcom/prisma"; - +import { describe, expect, it } from "vitest"; import { isAdminGuard } from "../../../lib/utils/isAdmin"; import { ScopeOfAdmin } from "../../../lib/utils/scopeOfAdmin"; @@ -12,53 +10,6 @@ type CustomNextApiRequest = NextApiRequest & Request; type CustomNextApiResponse = NextApiResponse & Response; describe("isAdmin guard", () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); - - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } - - const dunderOrg = await prisma.team.findFirst({ - where: { - slug: "dunder-mifflin", - isOrganization: true, - }, - }); - - if (dunderOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: dunderOrg.id, - }, - update: { - isAdminAPIEnabled: false, - }, - create: { - organizationId: dunderOrg.id, - orgAutoAcceptEmail: "dunder-mifflin.com", - isAdminAPIEnabled: false, - }, - }); - } - }); it("Returns false when user does not exist in the system", async () => { const { req } = createMocks({ method: "POST", diff --git a/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts b/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts index 66f180c4e05119..d9e1e6bba82c48 100644 --- a/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts +++ b/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts @@ -1,37 +1,11 @@ -import { describe, it, expect, beforeAll } from "vitest"; - import prisma from "@calcom/prisma"; - +import { describe, expect, it } from "vitest"; import { getAccessibleUsers, retrieveOrgScopedAccessibleUsers, } from "../../../lib/utils/retrieveScopedAccessibleUsers"; describe("retrieveScopedAccessibleUsers tests", () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); - - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } - }); describe("getAccessibleUsers", () => { it("Does not return members when only admin user ID is supplied", async () => { const adminUser = await prisma.user.findFirstOrThrow({ where: { email: "owner1-acme@example.com" } }); diff --git a/scripts/seed.ts b/scripts/seed.ts index 8ee0afecac88b1..154875b719209d 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -329,7 +329,12 @@ async function createOrganizationAndAddMembersAndTeams({ }); if (existingTeam) { - console.log(`Organization with slug '${orgData.slug}' already exists, skipping.`); + console.log(`Organization with slug '${orgData.slug}' already exists, ensuring settings are up to date.`); + await prisma.organizationSettings.upsert({ + where: { organizationId: existingTeam.id }, + update: { ...orgData.organizationSettings }, + create: { organizationId: existingTeam.id, ...orgData.organizationSettings }, + }); return; } @@ -341,14 +346,13 @@ async function createOrganizationAndAddMembersAndTeams({ }; })[] = []; - try { - const batchSize = 50; - // Process members in batches of in parallel - for (let i = 0; i < orgMembers.length; i += batchSize) { - const batch = orgMembers.slice(i, i + batchSize); + const batchSize = 50; + for (let i = 0; i < orgMembers.length; i += batchSize) { + const batch = orgMembers.slice(i, i + batchSize); - const batchResults = await Promise.all( - batch.map(async (member) => { + const batchResults = await Promise.all( + batch.map(async (member) => { + try { const newUser = await createUserAndEventType({ user: { ...member.memberData, @@ -375,13 +379,6 @@ async function createOrganizationAndAddMembersAndTeams({ ], }); - const orgMemberInDb = { - ...newUser, - inTeams: member.inTeams, - orgMembership: member.orgMembership, - orgProfile: member.orgProfile, - }; - // Create temp org redirect with upsert to handle duplicates await prisma.tempOrgRedirect.upsert({ where: { @@ -402,26 +399,31 @@ async function createOrganizationAndAddMembersAndTeams({ }, }); - return orgMemberInDb; - }) - ); + return { + ...newUser, + inTeams: member.inTeams, + orgMembership: member.orgMembership, + orgProfile: member.orgProfile, + }; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { + console.log(`Member ${member.memberData.username} already seeded, skipping`); + return null; + } + throw e; + } + }) + ); - orgMembersInDb.push(...batchResults); - } - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - if (e.code === "P2002") { - console.log(`One of the organization members already exists, skipping the entire seeding`); - return; - } - } - console.error(e); + orgMembersInDb.push(...batchResults.filter((r): r is NonNullable => r !== null)); } - await Promise.all([ + await Promise.all( usersOutsideOrg.map(async (user) => { - return await prisma.user.create({ - data: { + await prisma.user.upsert({ + where: { email_username: { email: user.email, username: user.username } }, + update: {}, + create: { username: user.username, name: user.name, email: user.email, @@ -433,8 +435,8 @@ async function createOrganizationAndAddMembersAndTeams({ }, }, }); - }), - ]); + }) + ); const { organizationSettings, ...restOrgData } = orgData; From 706c391650dec26817617faca53fcbd08b2e90c7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 06:15:04 +0000 Subject: [PATCH 2/2] fix: remove PII from P2002 log and recover existing user instead of returning null - Replace username interpolation in log message with generic text (Cubic violation #2, confidence 9/10) - On P2002, fetch the existing user from DB and return it with membership data instead of returning null, which was dropping users from org setup on retries (Cubic violation #3, confidence 9/10) Co-Authored-By: bot_apk --- scripts/seed.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/seed.ts b/scripts/seed.ts index 154875b719209d..9f57324f4f0d09 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -407,8 +407,22 @@ async function createOrganizationAndAddMembersAndTeams({ }; } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { - console.log(`Member ${member.memberData.username} already seeded, skipping`); - return null; + console.log("Organization member already seeded, skipping"); + const existingUser = await prisma.user.findUnique({ + where: { + email_username: { + email: member.memberData.email, + username: member.memberData.username, + }, + }, + }); + if (!existingUser) throw e; + return { + ...existingUser, + inTeams: member.inTeams, + orgMembership: member.orgMembership, + orgProfile: member.orgProfile, + }; } throw e; }