fix: update tempOrgRediret on updating org slug (#25838)

* fix: update tempOrgRediret on updating s
lug

* Update packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* refactor

* fix

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Anik Dhabal Babu
2025-12-18 13:18:56 +00:00
committed by GitHub
co-authored by cubic-dev-ai[bot]
parent 176aeaafd8
commit b20b52a1f8
3 changed files with 155 additions and 101 deletions
@@ -0,0 +1,146 @@
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import type { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
import { renameDomain } from "@calcom/lib/domainManager/organization";
import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers";
import { HttpError } from "@calcom/lib/http-error";
import type { Prisma, PrismaClient } from "@calcom/prisma/client";
import { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
import type { TAdminUpdate } from "@calcom/trpc/server/routers/viewer/organizations/adminUpdate.schema";
type AdminOrganizationUpdateServiceDeps = {
prismaClient: PrismaClient;
organizationRepository: OrganizationRepository;
};
export class AdminOrganizationUpdateService {
constructor(private readonly deps: AdminOrganizationUpdateServiceDeps) {}
async updateOrganization(input: TAdminUpdate) {
const { id, organizationSettings, ...restInput } = input;
const { organizationRepository } = this.deps;
const existingOrg = await organizationRepository.findByIdIncludeOrganizationSettings({ id });
if (!existingOrg) {
throw new HttpError({
message: "Organization not found",
statusCode: 404,
});
}
const { mergeMetadata } = getMetadataHelpers(
teamMetadataStrictSchema.unwrap(),
existingOrg.metadata || {}
);
const data: Prisma.TeamUpdateArgs["data"] = restInput;
const oldSlug = existingOrg.slug;
const newSlug = restInput.slug;
if (newSlug) {
await throwIfSlugConflicts({
id,
slug: newSlug,
teamRepository: new TeamRepository(this.deps.prismaClient),
});
const isSlugChanged = newSlug !== oldSlug;
if (isSlugChanged) {
// If slug is changed, we need to rename the domain first
// If renaming fails, we don't want to update the new slug in DB
await renameDomain(oldSlug, newSlug);
}
data.slug = newSlug;
data.metadata = mergeMetadata({
// If we save slug, we don't need the requestedSlug anymore
requestedSlug: undefined,
});
}
const updatedOrganization = await this.deps.prismaClient.$transaction(async (tx) => {
const updatedOrganization = await tx.team.update({
where: { id },
data,
});
// Update all TempOrgRedirect records that point to the old org URL to use the new org URL
if (newSlug && oldSlug && newSlug !== oldSlug) {
const oldOrgUrlPrefix = getOrgFullOrigin(oldSlug);
const newOrgUrlPrefix = getOrgFullOrigin(newSlug);
const redirectsToUpdate = await tx.tempOrgRedirect.findMany({
where: {
toUrl: {
startsWith: oldOrgUrlPrefix,
},
},
select: {
id: true,
toUrl: true,
},
});
for (const redirect of redirectsToUpdate) {
const newToUrl = redirect.toUrl.replace(oldOrgUrlPrefix, newOrgUrlPrefix);
await tx.tempOrgRedirect.update({
where: {
id: redirect.id,
},
data: {
toUrl: newToUrl,
},
});
}
}
if (organizationSettings || existingOrg.organizationSettings) {
await tx.organizationSettings.update({
where: {
organizationId: updatedOrganization.id,
},
data: {
isOrganizationConfigured:
organizationSettings?.isOrganizationConfigured ||
existingOrg.organizationSettings?.isOrganizationConfigured,
isOrganizationVerified:
organizationSettings?.isOrganizationVerified ||
existingOrg.organizationSettings?.isOrganizationVerified,
isAdminReviewed: organizationSettings?.isAdminReviewed,
orgAutoAcceptEmail:
organizationSettings?.orgAutoAcceptEmail ||
existingOrg.organizationSettings?.orgAutoAcceptEmail,
isAdminAPIEnabled: !!(
organizationSettings?.isAdminAPIEnabled ?? existingOrg.organizationSettings?.isAdminAPIEnabled
),
},
});
}
return updatedOrganization;
});
return updatedOrganization;
}
}
async function throwIfSlugConflicts({
id,
slug,
teamRepository,
}: {
id: number;
slug: string;
teamRepository: TeamRepository;
}) {
const isSlugAvailable = await teamRepository.isSlugAvailableForUpdate({
slug,
teamId: id,
parentId: null,
});
if (!isSlugAvailable) {
throw new HttpError({
message: "Organization or a Team with same slug already exists",
statusCode: 400,
});
}
}
@@ -6,7 +6,7 @@ import { createAProfileForAnExistingUser } from "@calcom/features/profile/lib/cr
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import type { PrismaClient } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
import type { CreationSource } from "@calcom/prisma/enums";
import type { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
@@ -227,6 +227,7 @@ export class OrganizationRepository {
},
select: {
...orgSelect,
metadata: true,
organizationSettings: true,
},
});
@@ -1,9 +1,6 @@
import { renameDomain } from "@calcom/lib/domainManager/organization";
import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers";
import { HttpError } from "@calcom/lib/http-error";
import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container";
import { AdminOrganizationUpdateService } from "@calcom/features/ee/organizations/lib/service/AdminOrganizationUpdateService";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "../../../types";
import type { TAdminUpdate } from "./adminUpdate.schema";
@@ -16,103 +13,13 @@ type AdminUpdateOptions = {
};
export const adminUpdateHandler = async ({ input }: AdminUpdateOptions) => {
const { id, organizationSettings, ...restInput } = input;
const existingOrg = await prisma.team.findUnique({
where: {
id: id,
},
include: {
organizationSettings: true,
},
const organizationRepository = getOrganizationRepository();
const adminOrganizationUpdateService = new AdminOrganizationUpdateService({
prismaClient: prisma,
organizationRepository,
});
if (!existingOrg) {
throw new HttpError({
message: "Organization not found",
statusCode: 404,
});
}
const { mergeMetadata } = getMetadataHelpers(teamMetadataStrictSchema.unwrap(), existingOrg.metadata || {});
const data: Prisma.TeamUpdateArgs["data"] = restInput;
if (restInput.slug) {
await throwIfSlugConflicts({ id, slug: restInput.slug });
const isSlugChanged = restInput.slug !== existingOrg.slug;
if (isSlugChanged) {
// If slug is changed, we need to rename the domain first
// If renaming fails, we don't want to update the new slug in DB
await renameDomain(existingOrg.slug, restInput.slug);
}
data.slug = input.slug;
data.metadata = mergeMetadata({
// If we save slug, we don't need the requestedSlug anymore
requestedSlug: undefined,
});
}
const updatedOrganization = await prisma.$transaction(async (tx) => {
const updatedOrganization = await tx.team.update({
where: { id },
data,
});
if (organizationSettings || existingOrg.organizationSettings) {
await tx.organizationSettings.update({
where: {
organizationId: updatedOrganization.id,
},
data: {
isOrganizationConfigured:
organizationSettings?.isOrganizationConfigured ||
existingOrg.organizationSettings?.isOrganizationConfigured,
isOrganizationVerified:
organizationSettings?.isOrganizationVerified ||
existingOrg.organizationSettings?.isOrganizationVerified,
isAdminReviewed: organizationSettings?.isAdminReviewed,
orgAutoAcceptEmail:
organizationSettings?.orgAutoAcceptEmail || existingOrg.organizationSettings?.orgAutoAcceptEmail,
isAdminAPIEnabled: !!(
organizationSettings?.isAdminAPIEnabled ?? existingOrg.organizationSettings?.isAdminAPIEnabled
),
},
});
}
return updatedOrganization;
});
return updatedOrganization;
return adminOrganizationUpdateService.updateOrganization(input);
};
export default adminUpdateHandler;
async function throwIfSlugConflicts({ id, slug }: { id: number; slug: string }) {
const organizationsWithSameSlug = await prisma.team.findMany({
where: {
slug: slug,
parentId: null,
},
});
if (organizationsWithSameSlug.length > 1) {
throw new HttpError({
message: "There can only be one organization with a given slug",
statusCode: 400,
});
}
const foundOrg = organizationsWithSameSlug[0];
if (!foundOrg) {
// No org with same slug found - So, no conflict.
return;
}
// If foundOrg isn't same as the org being edited
if (foundOrg.id !== id) {
throw new HttpError({
message: "Organization or a Team with same slug already exists",
statusCode: 400,
});
}
}