Files
calendar/packages/features/ee/organizations/lib/service/AdminOrganizationUpdateService.ts
T
Benny JooGitHubbenny@cal.com <sldisek783@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
431fbe44fc refactor: decouple @calcom/features from @calcom/trpc/server (#27751)
* make GetPublicEventInput

* space

* mv feature flags router to trpc

* remove trpc imports

* wip

* mv OrgMembershipLookup to features

* fix

* fix

* update

* fix

* revert formatting-only changes to improve PR reviewability

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-10 09:28:37 -03:00

156 lines
4.9 KiB
TypeScript

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 { orgSettingsSchema, teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
import { z } from "zod";
export const ZAdminUpdate = z.object({
id: z.number(),
name: z.string().optional(),
slug: z.string().nullish(),
organizationSettings: orgSettingsSchema.unwrap().optional(),
});
export type TAdminUpdate = z.infer<typeof ZAdminUpdate>;
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,
});
}
}