dc43eba23b
* feat: restore moveTeamToOrg admin endpoint for organization migration - Add moveTeamToOrg and removeTeamFromOrg functions to orgMigration.ts - Restore API endpoint at /api/orgMigration/moveTeamToOrg - Restore admin UI page at /settings/admin/orgMigrations/moveTeamToOrg - Add helper functions for team redirect management - Support moving team members along with the team This endpoint allows admins to migrate teams to organizations after org creation, which is needed as a temporary solution until proper org admin permissions are implemented. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: move moveTeamToOrg to lib/orgMigration and fix redirect URL - Move moveTeamToOrg and removeTeamFromOrg functions from playwright/lib to lib/orgMigration.ts - Update API endpoint to import from lib/orgMigration instead of playwright/lib - Fix redirect URL format: use / instead of /team/ - Fix import path: use ../playwright/lib/orgMigration instead of ./playwright/lib/orgMigration - Rename unused _dbRemoveTeamFromOrg in playwright file to satisfy linter - Remove duplicate functions from playwright/lib/orgMigration.ts This fixes the Vercel deployment failure caused by importing from test-only directories in production API routes, and corrects the redirect URL format to match the original implementation. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: reuse existing createTeamsHandler for moveTeamToOrg endpoint - Remove custom orgMigration.ts implementation - Update API endpoint to call existing createTeamsHandler with org owner impersonation - Remove moveMembers option from UI (always moves members by design) - Fix Vercel deployment by removing playwright import from production code - Use OrganizationRepository.adminFindById to fetch org owner Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: migrate moveTeamToOrg admin page and API to App Router - Move admin page from pages/settings/admin/orgMigrations to app/(use-page-wrapper)/settings/(admin-layout)/admin/orgMigrations - Convert API route from pages/api/orgMigration/moveTeamToOrg.ts to app/api/orgMigration/moveTeamToOrg/route.ts - Create client view component in modules/settings/admin/org-migrations/ - Remove old pages directory files and getServerSideProps Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: correct import paths for App Router compatibility - Fix @calcom/lib/server to @calcom/lib/server/i18n for getTranslation - Fix @calcom/ui barrel import to specific component paths Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use TFunction type for getFormSchema parameter Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use buildLegacyRequest for App Router session compatibility Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Remove unused fn * cleanup * cleanup * fix: ui * fix: handle slug conflict error when moving team to organization - Intercept Prisma P2002 unique constraint error when moving a team - Convert to user-friendly CONFLICT error with clear message - Add test case for slug conflict scenario Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use isPending instead of isLoading for tRPC mutation Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fixes * fix: remove PII (emails) from admin log statement Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: use instanceof pattern for Prisma error detection Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fixes * fix: use i18n key for slug conflict error message instead of hardcoded English string Co-Authored-By: bot_apk <apk@cognition.ai> * fix: narrow P2002 catch scope to only prisma.team.update call Separates the try-catch for prisma.team.update (slug conflict) from creditService.moveCreditsFromTeamToOrg to avoid misattributing credit service P2002 errors as slug conflicts. 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>
114 lines
2.9 KiB
TypeScript
114 lines
2.9 KiB
TypeScript
import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container";
|
|
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import slugify from "@calcom/lib/slugify";
|
|
import { CreationSource } from "@calcom/prisma/enums";
|
|
import { createTeamsHandler } from "@calcom/trpc/server/routers/viewer/organizations/createTeams.handler";
|
|
import { TRPCError } from "@trpc/server";
|
|
import type { TrpcSessionUser } from "../../../types";
|
|
import type { TMoveTeamToOrg } from "./moveTeamToOrg.schema";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["moveTeamToOrg"] });
|
|
|
|
type MoveTeamToOrgOptions = {
|
|
ctx: {
|
|
user: NonNullable<TrpcSessionUser>;
|
|
prisma: typeof import("@calcom/prisma").prisma;
|
|
};
|
|
input: TMoveTeamToOrg;
|
|
};
|
|
|
|
export const moveTeamToOrgHandler = async ({ ctx, input }: MoveTeamToOrgOptions) => {
|
|
const { teamId, targetOrgId, teamSlugInOrganization } = input;
|
|
|
|
log.debug(
|
|
"Moving team to org:",
|
|
safeStringify({
|
|
teamId,
|
|
targetOrgId,
|
|
teamSlugInOrganization,
|
|
})
|
|
);
|
|
|
|
const organizationRepository = getOrganizationRepository();
|
|
let org;
|
|
try {
|
|
org = await organizationRepository.adminFindById({ id: targetOrgId });
|
|
} catch (error) {
|
|
throw new TRPCError({ code: "NOT_FOUND", message: "organization_not_found" });
|
|
}
|
|
|
|
if (!org.members || org.members.length === 0) {
|
|
throw new TRPCError({ code: "BAD_REQUEST", message: "organization_owner_not_found" });
|
|
}
|
|
|
|
const teamRepository = new TeamRepository(ctx.prisma);
|
|
const team = await teamRepository.findById({ id: teamId });
|
|
|
|
if (!team) {
|
|
throw new TRPCError({ code: "NOT_FOUND", message: "team_not_found" });
|
|
}
|
|
|
|
if (team.parentId) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "cannot_move_subteam_already_in_org",
|
|
});
|
|
}
|
|
|
|
if (team.isOrganization) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "cannot_move_organization_to_organization",
|
|
});
|
|
}
|
|
|
|
const orgOwner = org.members[0].user;
|
|
|
|
log.info(
|
|
"Admin moving team to organization",
|
|
safeStringify({
|
|
adminUserId: ctx.user.id,
|
|
orgOwnerId: orgOwner.id,
|
|
teamId,
|
|
targetOrgId,
|
|
})
|
|
);
|
|
|
|
const oldTeamSlug = team.slug;
|
|
const newTeamSlug = slugify(teamSlugInOrganization);
|
|
|
|
await createTeamsHandler({
|
|
ctx: {
|
|
user: {
|
|
id: orgOwner.id,
|
|
organizationId: targetOrgId,
|
|
},
|
|
},
|
|
input: {
|
|
teamNames: [],
|
|
orgId: targetOrgId,
|
|
moveTeams: [
|
|
{
|
|
id: teamId,
|
|
newSlug: newTeamSlug,
|
|
shouldMove: true,
|
|
},
|
|
],
|
|
creationSource: CreationSource.WEBAPP,
|
|
},
|
|
});
|
|
|
|
return {
|
|
message: "team_moved_to_org_success",
|
|
teamId,
|
|
oldTeamSlug,
|
|
newTeamSlug,
|
|
organizationId: targetOrgId,
|
|
organizationSlug: org.slug,
|
|
};
|
|
};
|
|
|
|
export default moveTeamToOrgHandler;
|