fix: harden seed script org settings upsert and P2002 error handling (#28527)

* fix: harden seed script org settings upsert and P2002 error handling

Co-Authored-By: romitgabani1 <romitgabani1.work@gmail.com>

* 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 <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Romit
2026-03-24 21:43:50 +00:00
committed by GitHub
co-authored by bot_apk Devin AI Alex van Andel
parent e9e96671bd
commit ee973c6661
3 changed files with 52 additions and 111 deletions
@@ -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<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
@@ -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" } });
+49 -33
View File
@@ -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,45 @@ 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("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;
}
})
);
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<typeof r> => 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 +449,8 @@ async function createOrganizationAndAddMembersAndTeams({
},
},
});
}),
]);
})
);
const { organizationSettings, ...restOrgData } = orgData;