Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a0991c259 | ||
|
|
2e454c9723 | ||
|
|
7e6d94c8e0 | ||
|
|
e18b790ccd | ||
|
|
0274417ad1 | ||
|
|
a8a3a93ace | ||
|
|
88bbab216e | ||
|
|
f74434a282 | ||
|
|
ed6f9d8252 | ||
|
|
a19ccab16a | ||
|
|
77369a13f1 | ||
|
|
8231356d2a | ||
|
|
469a942b5a | ||
|
|
0afe6cf5d1 |
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
@@ -199,7 +199,7 @@ There's some settings that you'll need to setup.
|
||||
Under Vercel > Your API Project > Settings
|
||||
|
||||
In General > Build & Development Settings
|
||||
BUILD COMMAND: `yarn turbo run build --scope=@calcom/api --include-dependencies --no-deps`
|
||||
BUILD COMMAND: `yarn turbo run build --filter=@calcom/api --no-deps`
|
||||
OUTPUT DIRECTORY: `apps/api/.next`
|
||||
|
||||
In Git > Ignored Build Step
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.312",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.314",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
+17
-1
@@ -2,6 +2,8 @@ import { CreateOrgMembershipDto } from "@/modules/organizations/memberships/inpu
|
||||
import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { TeamService } from "@calcom/platform-libraries";
|
||||
|
||||
import { UpdateOrgMembershipDto } from "../inputs/update-organization-membership.input";
|
||||
import { OrganizationsMembershipOutputService } from "./organizations-membership-output.service";
|
||||
|
||||
@@ -62,10 +64,24 @@ export class OrganizationsMembershipService {
|
||||
}
|
||||
|
||||
async deleteOrgMembership(organizationId: number, membershipId: number) {
|
||||
const membership = await this.organizationsMembershipRepository.deleteOrgMembership(
|
||||
// Get the membership first to get the userId
|
||||
const membership = await this.organizationsMembershipRepository.findOrgMembership(
|
||||
organizationId,
|
||||
membershipId
|
||||
);
|
||||
|
||||
if (!membership) {
|
||||
throw new NotFoundException(
|
||||
`Membership with id ${membershipId} within organization id ${organizationId} not found`
|
||||
);
|
||||
}
|
||||
|
||||
await TeamService.removeMembers({
|
||||
teamIds: [organizationId],
|
||||
userIds: [membership.userId],
|
||||
isOrg: true,
|
||||
});
|
||||
|
||||
return this.organizationsMembershipOutputService.getOrgMembershipOutput(membership);
|
||||
}
|
||||
|
||||
|
||||
-4
@@ -20,7 +20,6 @@ import {
|
||||
OrgTeamMembershipOutputResponseDto,
|
||||
} from "@/modules/organizations/teams/memberships/outputs/organization-teams-memberships.output";
|
||||
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/teams/memberships/services/organizations-teams-memberships.service";
|
||||
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||
import { TeamMembershipOutput } from "@/modules/teams/memberships/outputs/team-membership.output";
|
||||
import {
|
||||
Controller,
|
||||
@@ -59,7 +58,6 @@ export class OrganizationsTeamsMembershipsController {
|
||||
|
||||
constructor(
|
||||
private organizationsTeamsMembershipsService: OrganizationsTeamsMembershipsService,
|
||||
private teamsEventTypesService: TeamsEventTypesService,
|
||||
private readonly organizationsRepository: OrganizationsRepository
|
||||
) {}
|
||||
|
||||
@@ -127,8 +125,6 @@ export class OrganizationsTeamsMembershipsController {
|
||||
membershipId
|
||||
);
|
||||
|
||||
await this.teamsEventTypesService.deleteUserTeamEventTypesAndHosts(membership.userId, teamId);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(TeamMembershipOutput, membership, { strategy: "excludeAll" }),
|
||||
|
||||
+13
-1
@@ -3,6 +3,8 @@ import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/teams/member
|
||||
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/teams/memberships/organizations-teams-memberships.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { TeamService } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsMembershipsService {
|
||||
constructor(
|
||||
@@ -58,11 +60,21 @@ export class OrganizationsTeamsMembershipsService {
|
||||
}
|
||||
|
||||
async deleteOrgTeamMembership(organizationId: number, teamId: number, membershipId: number) {
|
||||
const teamMembership = await this.organizationsTeamsMembershipsRepository.deleteOrgTeamMembershipById(
|
||||
// First get the membership to get the userId
|
||||
const teamMembership = await this.organizationsTeamsMembershipsRepository.findOrgTeamMembership(
|
||||
organizationId,
|
||||
teamId,
|
||||
membershipId
|
||||
);
|
||||
|
||||
if (!teamMembership) {
|
||||
throw new NotFoundException(
|
||||
`Membership with id ${membershipId} not found in team ${teamId} of organization ${organizationId}`
|
||||
);
|
||||
}
|
||||
|
||||
await TeamService.removeMembers({ teamIds: [teamId], userIds: [teamMembership.userId], isOrg: false });
|
||||
|
||||
return teamMembership;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -12,7 +12,12 @@ import {
|
||||
EMAIL,
|
||||
WorkflowEmailAttendeeStepDto,
|
||||
} from "@/modules/workflows/inputs/workflow-step.input";
|
||||
import { BEFORE_EVENT, DAY } from "@/modules/workflows/inputs/workflow-trigger.input";
|
||||
import {
|
||||
BEFORE_EVENT,
|
||||
DAY,
|
||||
OnAfterEventTriggerDto,
|
||||
OnBeforeEventTriggerDto,
|
||||
} from "@/modules/workflows/inputs/workflow-trigger.input";
|
||||
// Adjust path if needed
|
||||
import { GetWorkflowOutput, GetWorkflowsOutput } from "@/modules/workflows/outputs/workflow.output";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
@@ -255,6 +260,10 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
"+37255555555"
|
||||
);
|
||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 3)?.email).toEqual(authEmail);
|
||||
const trigger = sampleCreateWorkflowDto.trigger as OnBeforeEventTriggerDto;
|
||||
expect(responseBody.data.trigger?.offset?.value).toEqual(trigger.offset.value);
|
||||
expect(responseBody.data.trigger?.offset?.unit).toEqual(trigger.offset.unit);
|
||||
|
||||
createdWorkflowId = responseBody.data.id;
|
||||
createdWorkflow = responseBody.data;
|
||||
});
|
||||
@@ -330,6 +339,13 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
expect(step1).toBeDefined();
|
||||
const partialUpdateDto: Partial<CreateWorkflowDto> = {
|
||||
name: updatedName,
|
||||
trigger: {
|
||||
type: "afterEvent",
|
||||
offset: {
|
||||
unit: "minute",
|
||||
value: 10,
|
||||
},
|
||||
},
|
||||
steps: step1 ? [{ ...step1, sender: "updatedSender" } as WorkflowEmailAttendeeStepDto] : [],
|
||||
};
|
||||
expect(createdWorkflowId).toBeDefined();
|
||||
@@ -348,6 +364,10 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
step1 && expect(responseBody.data.steps[0].id).toEqual(step1.id);
|
||||
expect(responseBody.data.steps[0].sender).toEqual("updatedSender");
|
||||
expect(responseBody.data.steps[1]?.id).toBeUndefined();
|
||||
const trigger = partialUpdateDto.trigger as OnAfterEventTriggerDto;
|
||||
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
||||
expect(responseBody.data.trigger?.offset?.value).toEqual(trigger.offset.value);
|
||||
expect(responseBody.data.trigger?.offset?.unit).toEqual(trigger.offset.unit);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -156,17 +156,4 @@ export class TeamsEventTypesService {
|
||||
|
||||
return this.eventTypesRepository.deleteEventType(eventTypeId);
|
||||
}
|
||||
|
||||
async deleteUserTeamEventTypesAndHosts(userId: number, teamId: number) {
|
||||
try {
|
||||
await this.teamsEventTypesRepository.deleteUserManagedTeamEventTypes(userId, teamId);
|
||||
await this.teamsEventTypesRepository.removeUserFromTeamEventTypesHosts(userId, teamId);
|
||||
} catch (err) {
|
||||
this.logger.error("Could not remove user from all team event-types.", {
|
||||
error: err,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -3,7 +3,6 @@ import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||
import { CreateTeamMembershipInput } from "@/modules/teams/memberships/inputs/create-team-membership.input";
|
||||
import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/update-team-membership.input";
|
||||
import { CreateTeamMembershipOutput } from "@/modules/teams/memberships/outputs/create-team-membership.output";
|
||||
@@ -45,10 +44,7 @@ import { SkipTakePagination } from "@calcom/platform-types";
|
||||
export class TeamsMembershipsController {
|
||||
private logger = new Logger("TeamsMembershipsController");
|
||||
|
||||
constructor(
|
||||
private teamsMembershipsService: TeamsMembershipsService,
|
||||
private teamsEventTypesService: TeamsEventTypesService
|
||||
) {}
|
||||
constructor(private teamsMembershipsService: TeamsMembershipsService) {}
|
||||
|
||||
@Roles("TEAM_ADMIN")
|
||||
@Post("/")
|
||||
@@ -127,7 +123,6 @@ export class TeamsMembershipsController {
|
||||
membershipId,
|
||||
body
|
||||
);
|
||||
|
||||
if (!currentMembership.accepted && updatedMembership.accepted) {
|
||||
try {
|
||||
await updateNewTeamMemberEventTypes(updatedMembership.userId, teamId);
|
||||
@@ -151,8 +146,6 @@ export class TeamsMembershipsController {
|
||||
): Promise<DeleteTeamMembershipOutput> {
|
||||
const membership = await this.teamsMembershipsService.deleteTeamMembership(teamId, membershipId);
|
||||
|
||||
await this.teamsEventTypesService.deleteUserTeamEventTypesAndHosts(membership.userId, teamId);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(TeamMembershipOutput, membership, { strategy: "excludeAll" }),
|
||||
|
||||
@@ -3,6 +3,8 @@ import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/up
|
||||
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { TeamService } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class TeamsMembershipsService {
|
||||
constructor(private readonly teamsMembershipsRepository: TeamsMembershipsRepository) {}
|
||||
@@ -41,10 +43,15 @@ export class TeamsMembershipsService {
|
||||
}
|
||||
|
||||
async deleteTeamMembership(teamId: number, membershipId: number) {
|
||||
const teamMembership = await this.teamsMembershipsRepository.deleteTeamMembershipById(
|
||||
teamId,
|
||||
membershipId
|
||||
);
|
||||
// First get the membership to get the userId
|
||||
const teamMembership = await this.teamsMembershipsRepository.findTeamMembership(teamId, membershipId);
|
||||
|
||||
if (!teamMembership) {
|
||||
throw new NotFoundException(`Membership with id ${membershipId} not found in team ${teamId}`);
|
||||
}
|
||||
|
||||
await TeamService.removeMembers({ teamIds: [teamId], userIds: [teamMembership.userId], isOrg: false });
|
||||
|
||||
return teamMembership;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { TimeUnit, WorkflowTriggerEvents } from "@prisma/client";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsNumber, ValidateNested } from "class-validator";
|
||||
import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
export const BEFORE_EVENT = "beforeEvent";
|
||||
export const EVENT_CANCELLED = "eventCancelled";
|
||||
@@ -68,6 +68,8 @@ export class WorkflowTriggerOffsetDto {
|
||||
value!: number;
|
||||
|
||||
@ApiProperty({ description: "Unit for the offset time", example: HOUR })
|
||||
@IsString()
|
||||
@IsIn(TIME_UNITS)
|
||||
unit!: TimeUnitType;
|
||||
}
|
||||
|
||||
@@ -75,6 +77,8 @@ export class BaseWorkflowTriggerDto {
|
||||
@ApiProperty({
|
||||
description: "Trigger type for the workflow",
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([WORKFLOW_TRIGGER_TYPES])
|
||||
type!: WorkflowTriggerType;
|
||||
}
|
||||
|
||||
@@ -82,6 +86,8 @@ export class OnCreationTriggerDto {
|
||||
@ApiProperty({
|
||||
description: "Trigger type for the workflow",
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([NEW_EVENT])
|
||||
type: typeof NEW_EVENT = NEW_EVENT;
|
||||
}
|
||||
|
||||
@@ -89,12 +95,16 @@ export class OnRescheduleTriggerDto {
|
||||
@ApiProperty({
|
||||
description: "Trigger type for the workflow",
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([RESCHEDULE_EVENT])
|
||||
type: typeof RESCHEDULE_EVENT = RESCHEDULE_EVENT;
|
||||
}
|
||||
export class OnCancelTriggerDto {
|
||||
@ApiProperty({
|
||||
description: "Trigger type for the workflow",
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([EVENT_CANCELLED])
|
||||
type: typeof EVENT_CANCELLED = EVENT_CANCELLED;
|
||||
}
|
||||
|
||||
@@ -113,6 +123,8 @@ export class OnBeforeEventTriggerDto extends TriggerOffsetDTO {
|
||||
description: "Trigger type for the workflow",
|
||||
example: BEFORE_EVENT,
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([BEFORE_EVENT])
|
||||
type: typeof BEFORE_EVENT = BEFORE_EVENT;
|
||||
}
|
||||
|
||||
@@ -121,6 +133,8 @@ export class OnAfterEventTriggerDto extends TriggerOffsetDTO {
|
||||
description: "Trigger type for the workflow",
|
||||
example: AFTER_EVENT,
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([AFTER_EVENT])
|
||||
type: typeof AFTER_EVENT = AFTER_EVENT;
|
||||
}
|
||||
|
||||
@@ -129,6 +143,8 @@ export class OnAfterCalVideoGuestsNoShowTriggerDto extends TriggerOffsetDTO {
|
||||
description: "Trigger type for the workflow",
|
||||
example: AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([AFTER_GUESTS_CAL_VIDEO_NO_SHOW])
|
||||
type: typeof AFTER_GUESTS_CAL_VIDEO_NO_SHOW = AFTER_GUESTS_CAL_VIDEO_NO_SHOW;
|
||||
}
|
||||
|
||||
@@ -137,5 +153,7 @@ export class OnAfterCalVideoHostsNoShowTriggerDto extends TriggerOffsetDTO {
|
||||
description: "Trigger type for the workflow",
|
||||
example: AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||
})
|
||||
@IsString()
|
||||
@IsIn([AFTER_HOSTS_CAL_VIDEO_NO_SHOW])
|
||||
type: typeof AFTER_HOSTS_CAL_VIDEO_NO_SHOW = AFTER_HOSTS_CAL_VIDEO_NO_SHOW;
|
||||
}
|
||||
|
||||
@@ -59,4 +59,8 @@ export class EventTypesRepositoryFixture {
|
||||
async delete(eventTypeId: EventType["id"]) {
|
||||
return this.prismaWriteClient.eventType.delete({ where: { id: eventTypeId } });
|
||||
}
|
||||
|
||||
async findById(eventTypeId: EventType["id"]) {
|
||||
return this.prismaReadClient.eventType.findUnique({ where: { id: eventTypeId } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,8 @@ export class MembershipRepositoryFixture {
|
||||
await this.prismaWriteClient.user.update({ where: { id: user.id }, data: { organizationId: org.id } });
|
||||
return membership;
|
||||
}
|
||||
|
||||
async findById(membershipId: Membership["id"]) {
|
||||
return this.prismaReadClient.membership.findUnique({ where: { id: membershipId } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,13 @@ const InstalledAppsWrapper = async ({ params }: PageProps) => {
|
||||
createRouterCaller(appsRouter),
|
||||
]);
|
||||
|
||||
const connectedCalendars = await calendarsCaller.connectedCalendars();
|
||||
const installedCalendars = await appsCaller.integrations({
|
||||
variant: "calendar",
|
||||
onlyInstalled: true,
|
||||
});
|
||||
const [connectedCalendars, installedCalendars] = await Promise.all([
|
||||
calendarsCaller.connectedCalendars(),
|
||||
appsCaller.integrations({
|
||||
variant: "calendar",
|
||||
onlyInstalled: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return (
|
||||
<InstalledApps
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import type { AppImageProps, MeetingImageProps } from "@calcom/lib/OgImages";
|
||||
import { constructAppImage, constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
|
||||
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants";
|
||||
import { getCalcomUrl } from "@calcom/lib/getCalcomUrl";
|
||||
import { buildCanonical } from "@calcom/lib/next-seo.config";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { truncateOnWord } from "@calcom/lib/text";
|
||||
@@ -32,7 +33,7 @@ const _generateMetadataWithoutImage = async (
|
||||
const description = getDescription(t);
|
||||
const titleSuffix = `| ${APP_NAME}`;
|
||||
const displayedTitle = title.includes(titleSuffix) || hideBranding ? title : `${title} ${titleSuffix}`;
|
||||
const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL);
|
||||
const metadataBase = new URL(IS_CALCOM ? getCalcomUrl() : WEBAPP_URL);
|
||||
|
||||
return {
|
||||
title: title.length === 0 ? APP_NAME : displayedTitle,
|
||||
|
||||
@@ -9,6 +9,7 @@ import Script from "next/script";
|
||||
import "@calcom/embed-core/src/embed-iframe";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { IS_CALCOM, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getCalcomUrl } from "@calcom/lib/getCalcomUrl";
|
||||
import { buildCanonical } from "@calcom/lib/next-seo.config";
|
||||
import { IconSprites } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -64,7 +65,7 @@ function PageWrapper(props: AppProps) {
|
||||
// Set canonical to https://cal.com or self-hosted URL
|
||||
canonical={
|
||||
IS_CALCOM
|
||||
? buildCanonical({ path, origin: "https://cal.com" }) // cal.com & .dev
|
||||
? buildCanonical({ path, origin: getCalcomUrl() }) // cal.com & .dev
|
||||
: buildCanonical({ path, origin: WEBAPP_URL }) // self-hosted
|
||||
}
|
||||
{...seoConfig.defaultNextSeo}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
|
||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
const session = await getSession(ctx);
|
||||
if (!session || !session.user) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/login",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isAdmin = session.user.role === UserPermissionRole.ADMIN;
|
||||
if (!isAdmin) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
error: null,
|
||||
migrated: null,
|
||||
userId: session.user.id,
|
||||
username: session.user.username,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
|
||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
const session = await getSession(ctx);
|
||||
if (!session || !session.user) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/login",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isAdmin = session.user.role === UserPermissionRole.ADMIN;
|
||||
if (!isAdmin) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
|
||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
const session = await getSession(ctx);
|
||||
if (!session || !session.user) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/login",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isAdmin = session.user.role === UserPermissionRole.ADMIN;
|
||||
if (!isAdmin) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
|
||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
const session = await getSession(ctx);
|
||||
if (!session || !session.user) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/login",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isAdmin = session.user.role === UserPermissionRole.ADMIN;
|
||||
if (!isAdmin) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export function AvailabilityList({ availabilities }: AvailabilityListProps) {
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="text-default mb-16 mt-4 hidden text-center text-sm md:block">
|
||||
<div className="text-default mb-16 mt-4 block text-center text-sm">
|
||||
{t("temporarily_out_of_office")}{" "}
|
||||
<Link href="settings/my-account/out-of-office" className="underline">
|
||||
{t("add_a_redirect")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "5.5.18",
|
||||
"version": "5.6.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { test } from "./lib/fixtures";
|
||||
import {
|
||||
bookTimeSlot,
|
||||
confirmReschedule,
|
||||
confirmBooking,
|
||||
doOnOrgDomain,
|
||||
selectFirstAvailableTimeSlotNextMonth,
|
||||
selectSecondAvailableTimeSlotNextMonth,
|
||||
@@ -98,6 +99,58 @@ test("dynamic booking info prefilled by query params", async ({ page, users }) =
|
||||
activeState = await listItemLocator.getAttribute("data-active");
|
||||
expect(activeState).toEqual("false");
|
||||
});
|
||||
|
||||
test("multiple duration selection updates event length correctly", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
await user.apiLogin();
|
||||
|
||||
await test.step("update event title to include duration placeholder", async () => {
|
||||
await page.goto("/event-types");
|
||||
await page.waitForSelector('[data-testid="event-types"]');
|
||||
await page.click(`text=Multiple duration`);
|
||||
await page.waitForSelector('[data-testid="event-title"]');
|
||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
||||
"aria-current",
|
||||
"page"
|
||||
);
|
||||
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
||||
await page.locator('[data-testid="update-eventtype"]').click();
|
||||
await page.waitForResponse("/api/trpc/eventTypes/update?batch=1");
|
||||
});
|
||||
|
||||
await page.goto(`/${user.username}/multiple-duration`);
|
||||
|
||||
await page.locator('[data-testid="multiple-choice-30mins"]').waitFor({ state: "visible" });
|
||||
|
||||
await test.step("verify default 30min duration is selected", async () => {
|
||||
const duration30 = page.getByTestId("multiple-choice-30mins");
|
||||
const activeState = await duration30.getAttribute("data-active");
|
||||
expect(activeState).toEqual("true");
|
||||
});
|
||||
|
||||
await test.step("book with 90min duration and verify title", async () => {
|
||||
await page.getByTestId("multiple-choice-90mins").click();
|
||||
|
||||
const duration90 = page.getByTestId("multiple-choice-90mins");
|
||||
const activeState = await duration90.getAttribute("data-active");
|
||||
expect(activeState).toEqual("true");
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await page.fill('[name="name"]', "Test User");
|
||||
await page.fill('[name="email"]', "test@example.com");
|
||||
await confirmBooking(page);
|
||||
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
|
||||
const bookingTitle = page.locator('[data-testid="booking-title"]');
|
||||
await expect(bookingTitle).toBeVisible();
|
||||
const titleText = await bookingTitle.textContent();
|
||||
expect(titleText).toContain("90");
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip("it contains the right event details", async ({ page }) => {
|
||||
const response = await page.goto(`http://acme.cal.local:3000/owner1+member1`);
|
||||
|
||||
@@ -76,7 +76,7 @@ Sometimes, yarn install might fail during deployment on Vercel, in which case, y
|
||||
<Step title="Override the build command">
|
||||
Override to:
|
||||
```
|
||||
cd ../.. && yarn build --include-dependencies --no-deps
|
||||
cd ../.. && yarn build --no-deps
|
||||
```
|
||||
</Step>
|
||||
<Step title="Hit Deploy">
|
||||
|
||||
@@ -33,8 +33,8 @@ RUN set -eux; \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives && \
|
||||
yarn config set httpTimeout 1200000 && \
|
||||
npx turbo prune --scope=@calcom/web --docker && \
|
||||
npx turbo prune --scope=@calcom/api --docker && \
|
||||
npx turbo prune --filter=@calcom/web --docker && \
|
||||
npx turbo prune --filter=@calcom/api --docker && \
|
||||
yarn install && \
|
||||
yarn turbo run build --filter=@calcom/api
|
||||
|
||||
|
||||
+13
-13
@@ -28,23 +28,23 @@
|
||||
"edit-app-template": "yarn app-store edit-template",
|
||||
"delete-app-template": "yarn app-store delete-template",
|
||||
"build": "turbo run build --filter=@calcom/web...",
|
||||
"build:ai": "turbo run build --scope=\"@calcom/ai\"",
|
||||
"build:ai": "turbo run build --filter=\"@calcom/ai\"",
|
||||
"clean": "find . -name node_modules -o -name .next -o -name .turbo -o -name dist -type d -prune | xargs rm -rf",
|
||||
"db-deploy": "turbo run db-deploy",
|
||||
"db-seed": "turbo run db-seed",
|
||||
"db-studio": "yarn prisma studio",
|
||||
"deploy": "turbo run deploy",
|
||||
"dev:all": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\" --scope=\"@calcom/console\"",
|
||||
"dev:ai": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/api-proxy\" --scope=\"@calcom/api\" --scope=\"@calcom/ai\"",
|
||||
"dev:api": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/api-proxy\" --scope=\"@calcom/api\"",
|
||||
"dev:api:console": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/api-proxy\" --scope=\"@calcom/api\" --scope=\"@calcom/console\"",
|
||||
"dev:console": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/console\"",
|
||||
"dev:swagger": "turbo run dev --scope=\"@calcom/api-proxy\" --scope=\"@calcom/api\" --scope=\"@calcom/swagger\"",
|
||||
"dev:website": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\"",
|
||||
"dev": "turbo run dev --scope=\"@calcom/web\"",
|
||||
"build-storybook": "turbo run build --scope=\"@calcom/storybook\"",
|
||||
"dev:all": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/website\" --filter=\"@calcom/console\"",
|
||||
"dev:ai": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/api-proxy\" --filter=\"@calcom/api\" --filter=\"@calcom/ai\"",
|
||||
"dev:api": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/api-proxy\" --filter=\"@calcom/api\"",
|
||||
"dev:api:console": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/api-proxy\" --filter=\"@calcom/api\" --filter=\"@calcom/console\"",
|
||||
"dev:console": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/console\"",
|
||||
"dev:swagger": "turbo run dev --filter=\"@calcom/api-proxy\" --filter=\"@calcom/api\" --filter=\"@calcom/swagger\"",
|
||||
"dev:website": "turbo run dev --filter=\"@calcom/web\" --filter=\"@calcom/website\"",
|
||||
"dev": "turbo run dev --filter=\"@calcom/web\"",
|
||||
"build-storybook": "turbo run build --filter=\"@calcom/storybook\"",
|
||||
"dx": "turbo run dx",
|
||||
"i-dev": "infisical run -- turbo run dev --scope=\"@calcom/web\"",
|
||||
"i-dev": "infisical run -- turbo run dev --filter=\"@calcom/web\"",
|
||||
"i-dx": "infisical run -- turbo run dx",
|
||||
"i-gen-web-example-env": "infisical secrets generate-example-env --tags=web > .env.example",
|
||||
"i-gen-app-store-example-env": "infisical secrets generate-example-env --tags=appstore > .env.appStore.example",
|
||||
@@ -61,7 +61,7 @@
|
||||
"pre-commit": "lint-staged",
|
||||
"predev": "echo 'Checking env files'",
|
||||
"prisma": "yarn workspace @calcom/prisma prisma",
|
||||
"start": "turbo run start --scope=\"@calcom/web\"",
|
||||
"start": "turbo run start --filter=\"@calcom/web\"",
|
||||
"tdd": "vitest watch",
|
||||
"e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/web",
|
||||
"e2e:app-store": "QUICK=true yarn playwright test --project=@calcom/app-store",
|
||||
@@ -124,7 +124,7 @@
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"eslint": "^8.34.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"turbo": "^1.10.1"
|
||||
"turbo": "^2.5.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"types-ramda": "0.29.4",
|
||||
|
||||
@@ -55,7 +55,7 @@ export const EventMeta = ({
|
||||
timeZones,
|
||||
children,
|
||||
selectedTimeslot,
|
||||
roundRobinHideOrgAndTeam
|
||||
roundRobinHideOrgAndTeam,
|
||||
}: {
|
||||
event?: Pick<
|
||||
BookerEvent,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useRef } from "react";
|
||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { useShouldShowArrows } from "@calcom/features/apps/components/AllApps";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
@@ -45,7 +45,7 @@ export const EventDuration = ({
|
||||
const { t } = useLocale();
|
||||
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
|
||||
const isPlatform = useIsPlatform();
|
||||
const [selectedDuration, setSelectedDuration, state] = useBookerStore((state) => [
|
||||
const [selectedDuration, setSelectedDuration, state] = useBookerStoreContext((state) => [
|
||||
state.selectedDuration,
|
||||
state.setSelectedDuration,
|
||||
state.state,
|
||||
|
||||
@@ -550,6 +550,9 @@ async function handler(
|
||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN
|
||||
) {
|
||||
const bookingRepo = new BookingRepository(prisma);
|
||||
|
||||
const requiresPayment = !Number.isNaN(paymentAppData.price) && paymentAppData.price > 0;
|
||||
|
||||
const existingBooking = await bookingRepo.getValidBookingFromEventTypeForAttendee({
|
||||
eventTypeId,
|
||||
bookerEmail,
|
||||
@@ -559,13 +562,20 @@ async function handler(
|
||||
});
|
||||
|
||||
if (existingBooking) {
|
||||
const hasPayments = existingBooking.payment.length > 0;
|
||||
const isPaidBooking = existingBooking.paid || !hasPayments;
|
||||
|
||||
const shouldShowPaymentForm = requiresPayment && !isPaidBooking;
|
||||
|
||||
const firstPayment = shouldShowPaymentForm ? existingBooking.payment[0] : undefined;
|
||||
|
||||
const bookingResponse = {
|
||||
...existingBooking,
|
||||
user: {
|
||||
...existingBooking.user,
|
||||
email: null,
|
||||
},
|
||||
paymentRequired: false,
|
||||
paymentRequired: shouldShowPaymentForm,
|
||||
seatReferenceUid: "",
|
||||
};
|
||||
|
||||
@@ -574,8 +584,8 @@ async function handler(
|
||||
luckyUsers: bookingResponse.userId ? [bookingResponse.userId] : [],
|
||||
isDryRun,
|
||||
...(isDryRun ? { troubleshooterData } : {}),
|
||||
paymentUid: undefined,
|
||||
paymentId: undefined,
|
||||
paymentUid: firstPayment?.uid,
|
||||
paymentId: firstPayment?.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*
|
||||
* They don't intend to test what the apps logic should do, but rather test if the apps are called with the correct data. For testing that, once should write tests within each app.
|
||||
*/
|
||||
import prismaMock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import {
|
||||
createBookingScenario,
|
||||
getDate,
|
||||
@@ -3277,6 +3279,122 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
test(
|
||||
`Payment retry scenario - should return existing payment UID and prevent duplicate bookings when retrying canceled payment`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential(), getStripeAppCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Paid Event",
|
||||
description: "It's a test Paid Event",
|
||||
slotInterval: 30,
|
||||
requiresConfirmation: false,
|
||||
metadata: {
|
||||
apps: {
|
||||
stripe: {
|
||||
price: 100,
|
||||
enabled: true,
|
||||
currency: "usd",
|
||||
},
|
||||
},
|
||||
},
|
||||
length: 30,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["stripe-payment"]],
|
||||
});
|
||||
|
||||
await createBookingScenario(scenarioData);
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
const { paymentUid } = mockPaymentApp({
|
||||
metadataLookupKey: "stripe",
|
||||
appStoreLookupKey: "stripepayment",
|
||||
});
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "New York" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const firstBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
expect(firstBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
paymentUid: expect.any(String),
|
||||
paymentRequired: true,
|
||||
uid: expect.any(String),
|
||||
})
|
||||
);
|
||||
|
||||
const firstBookingInDb = await prismaMock.booking.findUnique({
|
||||
where: { uid: firstBooking.uid },
|
||||
include: { payment: true },
|
||||
});
|
||||
|
||||
expect(firstBookingInDb).toBeTruthy();
|
||||
expect(firstBookingInDb?.payment).toHaveLength(1);
|
||||
expect(firstBookingInDb?.paid).toBe(false);
|
||||
|
||||
const secondBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
expect(secondBooking.uid).toBe(firstBooking.uid);
|
||||
expect(secondBooking.paymentUid).toBe(firstBooking.paymentUid);
|
||||
expect(secondBooking.paymentRequired).toBe(true);
|
||||
|
||||
const bookingsInDb = await prismaMock.booking.findMany({
|
||||
where: {
|
||||
eventTypeId: 1,
|
||||
attendees: {
|
||||
some: {
|
||||
email: booker.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { payment: true },
|
||||
});
|
||||
|
||||
expect(bookingsInDb).toHaveLength(1);
|
||||
expect(bookingsInDb[0].payment).toHaveLength(1);
|
||||
expect(bookingsInDb[0].uid).toBe(firstBooking.uid);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import removeMember from "@calcom/features/ee/teams/lib/removeMember";
|
||||
import { TeamService } from "@calcom/lib/server/service/teamService";
|
||||
|
||||
const removeUserFromOrg = async ({ userId, orgId }: { userId: number; orgId: number }) => {
|
||||
return removeMember({
|
||||
memberId: userId,
|
||||
teamId: orgId,
|
||||
isOrg: true,
|
||||
});
|
||||
return TeamService.removeMembers({ teamIds: [orgId], userIds: [userId], isOrg: true });
|
||||
};
|
||||
|
||||
export default removeUserFromOrg;
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { deleteWorkfowRemindersOfRemovedMember } from "./deleteWorkflowRemindersOfRemovedMember";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["removeMember"] });
|
||||
|
||||
const removeMember = async ({
|
||||
memberId,
|
||||
teamId,
|
||||
isOrg,
|
||||
}: {
|
||||
memberId: number;
|
||||
teamId: number;
|
||||
isOrg: boolean;
|
||||
}) => {
|
||||
const [membership] = await prisma.$transaction([
|
||||
prisma.membership.delete({
|
||||
where: {
|
||||
userId_teamId: { userId: memberId, teamId: teamId },
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
team: true,
|
||||
},
|
||||
}),
|
||||
// remove user as host from team events associated with this membership
|
||||
prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: memberId,
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const team = await prisma.team.findUnique({
|
||||
where: { id: teamId },
|
||||
select: {
|
||||
isOrganization: true,
|
||||
organizationSettings: true,
|
||||
id: true,
|
||||
metadata: true,
|
||||
activeOrgWorkflows: true,
|
||||
parentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const foundUser = await prisma.user.findUnique({
|
||||
where: { id: memberId },
|
||||
select: {
|
||||
id: true,
|
||||
movedToProfileId: true,
|
||||
email: true,
|
||||
username: true,
|
||||
completedOnboarding: true,
|
||||
teams: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
parentId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team || !foundUser) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
|
||||
if (isOrg) {
|
||||
log.debug("Removing a member from the organization");
|
||||
// Deleting membership from all child teams
|
||||
// Delete all sub-team memberships where this team is the organization
|
||||
await prisma.membership.deleteMany({
|
||||
where: {
|
||||
team: {
|
||||
parentId: teamId,
|
||||
},
|
||||
userId: membership.userId,
|
||||
},
|
||||
});
|
||||
|
||||
const userToDeleteMembershipOf = foundUser;
|
||||
|
||||
const profileToDelete = await ProfileRepository.findByUserIdAndOrgId({
|
||||
userId: userToDeleteMembershipOf.id,
|
||||
organizationId: team.id,
|
||||
});
|
||||
|
||||
if (
|
||||
userToDeleteMembershipOf.username &&
|
||||
userToDeleteMembershipOf.movedToProfileId === profileToDelete?.id
|
||||
) {
|
||||
log.debug("Cleaning up tempOrgRedirect for user", userToDeleteMembershipOf.username);
|
||||
|
||||
await prisma.tempOrgRedirect.deleteMany({
|
||||
where: {
|
||||
from: userToDeleteMembershipOf.username,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.user.update({
|
||||
where: { id: membership.userId },
|
||||
data: { organizationId: null },
|
||||
}),
|
||||
ProfileRepository.delete({
|
||||
userId: membership.userId,
|
||||
organizationId: team.id,
|
||||
}),
|
||||
prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: memberId,
|
||||
eventType: {
|
||||
team: {
|
||||
parentId: teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
// Deleted managed event types from this team from this member
|
||||
await prisma.eventType.deleteMany({
|
||||
where: { parent: { teamId: teamId }, userId: membership.userId },
|
||||
});
|
||||
|
||||
await deleteWorkfowRemindersOfRemovedMember(team, memberId, isOrg);
|
||||
|
||||
return { membership };
|
||||
};
|
||||
|
||||
export default removeMember;
|
||||
@@ -110,13 +110,15 @@ describe("RoleManagementFactory", () => {
|
||||
it("should allow role change when user has permission", async () => {
|
||||
mockPermissionCheckService.checkPermission.mockResolvedValue(true);
|
||||
const manager = await factory.createRoleManager(organizationId);
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
manager.checkPermissionToChangeRole(userId, organizationId, "org")
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it("should throw UNAUTHORIZED when user lacks permission", async () => {
|
||||
mockPermissionCheckService.checkPermission.mockResolvedValue(false);
|
||||
const manager = await factory.createRoleManager(organizationId);
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId)).rejects.toThrow(
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId, "org")).rejects.toThrow(
|
||||
new RoleManagementError(
|
||||
"You do not have permission to change roles",
|
||||
RoleManagementErrorCode.UNAUTHORIZED
|
||||
@@ -193,13 +195,15 @@ describe("RoleManagementFactory", () => {
|
||||
customRoleId: null,
|
||||
});
|
||||
const manager = await factory.createRoleManager(organizationId);
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
manager.checkPermissionToChangeRole(userId, organizationId, "org")
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it("should throw UNAUTHORIZED when user is not owner", async () => {
|
||||
vi.mocked(isOrganisationAdmin).mockResolvedValue(false);
|
||||
const manager = await factory.createRoleManager(organizationId);
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId)).rejects.toThrow(
|
||||
await expect(manager.checkPermissionToChangeRole(userId, organizationId, "org")).rejects.toThrow(
|
||||
new RoleManagementError(
|
||||
"Only owners or admin can update roles",
|
||||
RoleManagementErrorCode.UNAUTHORIZED
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
@@ -10,7 +11,7 @@ import { RoleService } from "./role.service";
|
||||
|
||||
interface IRoleManager {
|
||||
isPBACEnabled: boolean;
|
||||
checkPermissionToChangeRole(userId: number, organizationId: number): Promise<void>;
|
||||
checkPermissionToChangeRole(userId: number, targetId: number, scope: "org" | "team"): Promise<void>;
|
||||
assignRole(
|
||||
userId: number,
|
||||
organizationId: number,
|
||||
@@ -29,11 +30,11 @@ class PBACRoleManager implements IRoleManager {
|
||||
private readonly permissionCheckService: PermissionCheckService
|
||||
) {}
|
||||
|
||||
async checkPermissionToChangeRole(userId: number, organizationId: number): Promise<void> {
|
||||
async checkPermissionToChangeRole(userId: number, targetId: number, scope: "org" | "team"): Promise<void> {
|
||||
const hasPermission = await this.permissionCheckService.checkPermission({
|
||||
userId,
|
||||
teamId: organizationId,
|
||||
permission: "organization.changeMemberRole",
|
||||
teamId: targetId,
|
||||
permission: scope === "team" ? "team.changeMemberRole" : "organization.changeMemberRole",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
@@ -95,9 +96,11 @@ class PBACRoleManager implements IRoleManager {
|
||||
|
||||
class LegacyRoleManager implements IRoleManager {
|
||||
public isPBACEnabled = false;
|
||||
async checkPermissionToChangeRole(userId: number, organizationId: number): Promise<void> {
|
||||
const membership = await isOrganisationAdmin(userId, organizationId);
|
||||
|
||||
async checkPermissionToChangeRole(userId: number, targetId: number, scope: "org" | "team"): Promise<void> {
|
||||
const membership =
|
||||
scope === "team"
|
||||
? !!(await isTeamAdmin(userId, targetId))
|
||||
: !!(await isOrganisationAdmin(userId, targetId));
|
||||
// Only OWNER/ADMIN can update role
|
||||
if (!membership) {
|
||||
throw new RoleManagementError(
|
||||
|
||||
@@ -45,7 +45,8 @@ export const IS_CALCOM =
|
||||
(new URL(WEBAPP_URL).hostname.endsWith("cal.com") ||
|
||||
new URL(WEBAPP_URL).hostname.endsWith("cal.dev") ||
|
||||
new URL(WEBAPP_URL).hostname.endsWith("cal.qa") ||
|
||||
new URL(WEBAPP_URL).hostname.endsWith("cal-staging.com"));
|
||||
new URL(WEBAPP_URL).hostname.endsWith("cal-staging.com") ||
|
||||
new URL(WEBAPP_URL).hostname.endsWith("cal.eu"));
|
||||
|
||||
export const CONSOLE_URL =
|
||||
new URL(WEBAPP_URL).hostname.endsWith(".cal.dev") ||
|
||||
|
||||
@@ -5,8 +5,8 @@ import type {
|
||||
InsightsRoutingServicePublicOptions,
|
||||
InsightsRoutingServiceFilterOptions,
|
||||
InsightsRoutingBaseService,
|
||||
} from "@calcom/lib/server/service/insightsRoutingBase";
|
||||
import type { InsightsRoutingService } from "@calcom/lib/server/service/insightsRoutingDI";
|
||||
} from "@calcom/lib/server/service/InsightsRoutingBaseService";
|
||||
import type { InsightsRoutingService } from "@calcom/lib/server/service/InsightsRoutingDIService";
|
||||
import { prismaModule } from "@calcom/prisma/prisma.module";
|
||||
|
||||
import { insightsRoutingModule } from "../modules/InsightsRouting";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createModule } from "@evyweb/ioctopus";
|
||||
|
||||
import type { IInsightsRoutingService } from "@calcom/lib/server/service/insightsRoutingDI";
|
||||
import { InsightsRoutingService } from "@calcom/lib/server/service/insightsRoutingDI";
|
||||
import type { IInsightsRoutingService } from "@calcom/lib/server/service/InsightsRoutingDIService";
|
||||
import { InsightsRoutingService } from "@calcom/lib/server/service/InsightsRoutingDIService";
|
||||
|
||||
import { DI_TOKENS } from "../tokens";
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { WEBAPP_URL, IS_CALCOM } from "./constants";
|
||||
|
||||
export const getCalcomUrl = () => {
|
||||
if (IS_CALCOM) {
|
||||
return new URL(WEBAPP_URL).hostname.endsWith("cal.eu") ? "https://cal.eu" : "https://cal.com";
|
||||
}
|
||||
return WEBAPP_URL;
|
||||
};
|
||||
@@ -828,6 +828,7 @@ export class BookingRepository {
|
||||
attendees: true,
|
||||
references: true,
|
||||
user: true,
|
||||
payment: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
InsightsRoutingBaseService,
|
||||
type InsightsRoutingServicePublicOptions,
|
||||
type InsightsRoutingServiceFilterOptions,
|
||||
} from "./insightsRoutingBase";
|
||||
} from "./InsightsRoutingBaseService";
|
||||
|
||||
export interface IInsightsRoutingService {
|
||||
prisma: typeof readonlyPrisma;
|
||||
@@ -7,7 +7,7 @@ import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus, MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { InsightsRoutingBaseService as InsightsRoutingService } from "../../service/insightsRoutingBase";
|
||||
import { InsightsRoutingBaseService as InsightsRoutingService } from "../../service/InsightsRoutingBaseService";
|
||||
|
||||
// SQL condition constants for testing
|
||||
const NOTHING_CONDITION = Prisma.sql`1=0`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,52 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { TeamBilling } from "@calcom/features/ee/billing/teams";
|
||||
import removeMember from "@calcom/features/ee/teams/lib/removeMember";
|
||||
import { deleteWorkfowRemindersOfRemovedMember } from "@calcom/features/ee/teams/lib/deleteWorkflowRemindersOfRemovedMember";
|
||||
import { deleteDomain } from "@calcom/lib/domainManager/organization";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { TeamRepository } from "@calcom/lib/server/repository/team";
|
||||
import { WorkflowService } from "@calcom/lib/server/service/workflows";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Membership } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["TeamService"] });
|
||||
|
||||
type MembershipWithRelations = Pick<
|
||||
Membership,
|
||||
"id" | "userId" | "teamId" | "role" | "accepted" | "disableImpersonation"
|
||||
>;
|
||||
|
||||
type TeamWithSettings = {
|
||||
id: number;
|
||||
isOrganization: boolean | null;
|
||||
organizationSettings: unknown;
|
||||
metadata: unknown;
|
||||
activeOrgWorkflows: unknown;
|
||||
parentId: number | null;
|
||||
};
|
||||
|
||||
type UserWithTeams = {
|
||||
id: number;
|
||||
movedToProfileId: number | null;
|
||||
email: string;
|
||||
username: string | null;
|
||||
completedOnboarding: boolean;
|
||||
teams: {
|
||||
team: {
|
||||
id: number;
|
||||
parentId: number | null;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
export type RemoveMemberResult = {
|
||||
membership: MembershipWithRelations;
|
||||
};
|
||||
|
||||
export class TeamService {
|
||||
/**
|
||||
* Deletes a team and all its associated data in a safe, transactional order.
|
||||
@@ -45,15 +81,23 @@ export class TeamService {
|
||||
return deletedTeam;
|
||||
}
|
||||
|
||||
static async removeMembers(teamIds: number[], memberIds: number[], isOrg = false) {
|
||||
const deleteMembershipPromises = [];
|
||||
static async removeMembers({
|
||||
teamIds,
|
||||
userIds,
|
||||
isOrg = false,
|
||||
}: {
|
||||
teamIds: number[];
|
||||
userIds: number[];
|
||||
isOrg?: boolean;
|
||||
}) {
|
||||
const deleteMembershipPromises: Promise<RemoveMemberResult>[] = [];
|
||||
|
||||
for (const memberId of memberIds) {
|
||||
for (const userId of userIds) {
|
||||
for (const teamId of teamIds) {
|
||||
deleteMembershipPromises.push(
|
||||
removeMember({
|
||||
TeamService.removeMember({
|
||||
teamId,
|
||||
memberId,
|
||||
userId,
|
||||
isOrg,
|
||||
})
|
||||
);
|
||||
@@ -121,4 +165,217 @@ export class TeamService {
|
||||
const teamBilling = await TeamBilling.findAndInit(teamId);
|
||||
return teamBilling.publish();
|
||||
}
|
||||
|
||||
private static async removeMember({
|
||||
userId,
|
||||
teamId,
|
||||
isOrg,
|
||||
}: {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
isOrg: boolean;
|
||||
}) {
|
||||
const membership = await TeamService.fetchMembershipOrThrow(userId, teamId);
|
||||
const team = await TeamService.fetchTeamOrThrow(teamId);
|
||||
const user = await TeamService.fetchUserOrThrow(userId);
|
||||
|
||||
if (isOrg) {
|
||||
log.debug("Removing a member from the organization");
|
||||
await TeamService.removeFromOrganization(membership, team, user);
|
||||
} else {
|
||||
log.debug("Removing a member from a team");
|
||||
await TeamService.removeFromTeam(membership, teamId);
|
||||
}
|
||||
|
||||
await deleteWorkfowRemindersOfRemovedMember(team, userId, isOrg);
|
||||
|
||||
return { membership };
|
||||
}
|
||||
|
||||
// TODO: Needs to be moved to repository
|
||||
private static async fetchMembershipOrThrow(
|
||||
userId: number,
|
||||
teamId: number
|
||||
): Promise<MembershipWithRelations> {
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: { userId: userId, teamId: teamId },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
role: true,
|
||||
accepted: true,
|
||||
disableImpersonation: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Membership not found" });
|
||||
}
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
// TODO: Needs to be moved to repository
|
||||
private static async fetchTeamOrThrow(teamId: number): Promise<TeamWithSettings> {
|
||||
const team = await prisma.team.findUnique({
|
||||
where: { id: teamId },
|
||||
select: {
|
||||
isOrganization: true,
|
||||
organizationSettings: true,
|
||||
id: true,
|
||||
metadata: true,
|
||||
activeOrgWorkflows: true,
|
||||
parentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Team not found" });
|
||||
}
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
// TODO: Needs to be moved to repository
|
||||
private static async fetchUserOrThrow(userId: number): Promise<UserWithTeams> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
movedToProfileId: true,
|
||||
email: true,
|
||||
username: true,
|
||||
completedOnboarding: true,
|
||||
teams: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
parentId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
// TODO: Needs to be moved to repository
|
||||
private static async cleanupTempOrgRedirect(user: UserWithTeams, team: TeamWithSettings) {
|
||||
const profileToDelete = await ProfileRepository.findByUserIdAndOrgId({
|
||||
userId: user.id,
|
||||
organizationId: team.id,
|
||||
});
|
||||
|
||||
if (user.username && user.movedToProfileId === profileToDelete?.id) {
|
||||
log.debug("Cleaning up tempOrgRedirect for user", user.username);
|
||||
await prisma.tempOrgRedirect.deleteMany({
|
||||
where: {
|
||||
from: user.username,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async removeFromOrganization(
|
||||
membership: MembershipWithRelations,
|
||||
team: TeamWithSettings,
|
||||
user: UserWithTeams
|
||||
) {
|
||||
await TeamService.cleanupTempOrgRedirect(user, team);
|
||||
const newUsername = generateNewUsername(user);
|
||||
|
||||
await prisma.$transaction([
|
||||
// Remove user from all sub-teams event type hosts
|
||||
prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: membership.userId,
|
||||
eventType: {
|
||||
team: {
|
||||
parentId: team.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Delete managed child events in sub-teams
|
||||
prisma.eventType.deleteMany({
|
||||
where: {
|
||||
userId: membership.userId,
|
||||
parent: {
|
||||
team: {
|
||||
parentId: team.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Remove organizationId from the user
|
||||
prisma.user.update({
|
||||
where: { id: membership.userId },
|
||||
data: {
|
||||
organizationId: null,
|
||||
username: newUsername,
|
||||
},
|
||||
}),
|
||||
// Delete the profile of the user from the organization
|
||||
ProfileRepository.delete({
|
||||
userId: membership.userId,
|
||||
organizationId: team.id,
|
||||
}),
|
||||
// Delete all sub-team memberships where this team is the organization
|
||||
prisma.membership.deleteMany({
|
||||
where: {
|
||||
team: {
|
||||
parentId: team.id,
|
||||
},
|
||||
userId: membership.userId,
|
||||
},
|
||||
}),
|
||||
// Delete the membership of the user from the organization
|
||||
prisma.membership.delete({
|
||||
where: {
|
||||
userId_teamId: { userId: membership.userId, teamId: team.id },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
// Generate new username for user leaving organization
|
||||
function generateNewUsername(user: UserWithTeams): string | null {
|
||||
// We ensure that new username would be unique across all users in the global namespace outside any organization
|
||||
return user.username != null ? `${user.username}-${user.id}` : null;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove member from regular team
|
||||
private static async removeFromTeam(membership: MembershipWithRelations, teamId: number) {
|
||||
await prisma.$transaction([
|
||||
// Remove user from all team event types' hosts
|
||||
prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: membership.userId,
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Deleted managed event types from this team for this member
|
||||
prisma.eventType.deleteMany({
|
||||
where: { parent: { teamId: teamId }, userId: membership.userId },
|
||||
}),
|
||||
// Delete the membership of the user from the team
|
||||
prisma.membership.delete({
|
||||
where: {
|
||||
userId_teamId: { userId: membership.userId, teamId: teamId },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +120,5 @@ export { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
|
||||
export { verifyPhoneNumber, sendVerificationCode };
|
||||
|
||||
export { TeamService } from "@calcom/lib/server/service/teamService";
|
||||
export { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
|
||||
Binary file not shown.
@@ -47,7 +47,7 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => {
|
||||
const roleManager = await RoleManagementFactory.getInstance().createRoleManager(organizationId);
|
||||
|
||||
try {
|
||||
await roleManager.checkPermissionToChangeRole(userId, organizationId);
|
||||
await roleManager.checkPermissionToChangeRole(userId, organizationId, "org");
|
||||
} catch (error) {
|
||||
if (error instanceof RoleManagementError) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: error.message });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
|
||||
import { isTeamAdmin, isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||
import { isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||
import { TeamRepository } from "@calcom/lib/server/repository/team";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -32,7 +32,7 @@ export const changeMemberRoleHandler = async ({ ctx, input }: ChangeMemberRoleOp
|
||||
|
||||
// Check permission to change roles
|
||||
try {
|
||||
await roleManager.checkPermissionToChangeRole(ctx.user.id, organizationId);
|
||||
await roleManager.checkPermissionToChangeRole(ctx.user.id, input.teamId, "team");
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -45,8 +45,6 @@ export const changeMemberRoleHandler = async ({ ctx, input }: ChangeMemberRoleOp
|
||||
typeof input.role === "string" &&
|
||||
Object.values(MembershipRole).includes(input.role as MembershipRole)
|
||||
) {
|
||||
// Traditional role assignment logic
|
||||
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
// Only owners can award owner role.
|
||||
if (input.role === MembershipRole.OWNER && !(await isTeamOwner(ctx.user?.id, input.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
@@ -59,7 +59,7 @@ export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) =
|
||||
message: "You can not remove yourself from a team you own.",
|
||||
});
|
||||
|
||||
await TeamService.removeMembers(teamIds, memberIds, isOrg);
|
||||
await TeamService.removeMembers({ teamIds, userIds: memberIds, isOrg });
|
||||
};
|
||||
|
||||
export default removeMemberHandler;
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
// TODO: Bring this test back with the correct setup (no illegal imports)
|
||||
import prismaMock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { describe, test, expect } from "vitest";
|
||||
|
||||
import { SchedulingType, MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import removeMember from "./removeMember.handler";
|
||||
|
||||
describe.skip("removeMember", () => {
|
||||
describe("should remove a member from a team", () => {
|
||||
test(`1) Should remove a member from a team
|
||||
2) Should remove the member from hosts
|
||||
`, async () => {
|
||||
const org = await createOrganization({
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
});
|
||||
|
||||
// Create the child team
|
||||
const childTeam = {
|
||||
id: 202,
|
||||
name: "Team 1",
|
||||
slug: "team-1",
|
||||
parentId: org.id,
|
||||
};
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
organizationId: org.id,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
teams: [
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
team: {
|
||||
id: org.id,
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
},
|
||||
},
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
team: {
|
||||
id: childTeam.id,
|
||||
name: "Team 1",
|
||||
slug: "team-1",
|
||||
parentId: org.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const otherTeamMembers = [
|
||||
{
|
||||
name: "Other Team Member 1",
|
||||
username: "other-team-member-1",
|
||||
timeZone: Timezones["+5:30"],
|
||||
defaultScheduleId: null,
|
||||
email: "other-team-member-1@example.com",
|
||||
id: 102,
|
||||
organizationId: org.id,
|
||||
schedules: [TestData.schedules.IstEveningShift],
|
||||
teams: [
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
role: MembershipRole.MEMBER,
|
||||
},
|
||||
team: {
|
||||
id: org.id,
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
},
|
||||
},
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
role: MembershipRole.MEMBER,
|
||||
},
|
||||
team: {
|
||||
id: childTeam.id,
|
||||
name: "Team 1",
|
||||
slug: "team-1",
|
||||
parentId: org.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await createBookingScenario(
|
||||
getScenarioData(
|
||||
{
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
teamId: childTeam.id,
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
length: 30,
|
||||
hosts: [
|
||||
{
|
||||
userId: 101,
|
||||
isFixed: false,
|
||||
},
|
||||
{
|
||||
userId: 102,
|
||||
isFixed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
usersApartFromOrganizer: otherTeamMembers,
|
||||
apps: [TestData.apps["daily-video"]],
|
||||
},
|
||||
org
|
||||
)
|
||||
);
|
||||
|
||||
//Logged in user is admin of the org
|
||||
const ctx = {
|
||||
user: {
|
||||
id: organizer.id,
|
||||
name: organizer.name,
|
||||
} as NonNullable<TrpcSessionUser>,
|
||||
};
|
||||
|
||||
await removeMember({
|
||||
ctx,
|
||||
input: {
|
||||
teamIds: [org.id],
|
||||
memberIds: [102],
|
||||
isOrg: true,
|
||||
},
|
||||
});
|
||||
|
||||
//Check if the remaining memberships are correct
|
||||
const remainingMemberships = await prismaMock.membership.count({
|
||||
where: {
|
||||
teamId: childTeam.id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(remainingMemberships).toBe(1);
|
||||
|
||||
//Check if the event type has the correct hosts
|
||||
const remainingHostsCount = await prismaMock.host.count({
|
||||
where: {
|
||||
eventTypeId: 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(remainingHostsCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"extends": "@calcom/tsconfig/react-library.json",
|
||||
"paths": {
|
||||
"~/*": ["/*"],
|
||||
"@calcom/app-store": ["../app-store"],
|
||||
"@calcom/app-store/*": ["../app-store/*"],
|
||||
"@calcom/dayjs": ["../dayjs"],
|
||||
"@calcom/dayjs/*": ["../dayjs/*"],
|
||||
"@calcom/features": ["../features"],
|
||||
"@calcom/features/*": ["../features/*"],
|
||||
"@calcom/lib": ["../lib"],
|
||||
"@calcom/lib/*": ["../lib/*"],
|
||||
"@calcom/prisma": ["../prisma"],
|
||||
"@calcom/prisma/*": ["../prisma/*"],
|
||||
"@calcom/repository/*": ["../lib/server/repository/*"]
|
||||
},
|
||||
"include": [
|
||||
"../types/@wojtekmaj__react-daterange-picker.d.ts",
|
||||
"../types/business-days-plugin.d.ts",
|
||||
@@ -33,6 +19,9 @@
|
||||
"declarationMap": false,
|
||||
"resolveJsonModule": true,
|
||||
"declarationDir": "types/server",
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+254
-253
@@ -1,6 +1,257 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"pipeline": {
|
||||
"globalDependencies": ["yarn.lock"],
|
||||
"globalEnv": [
|
||||
"ALLOWED_HOSTNAMES",
|
||||
"ANALYZE",
|
||||
"API_KEY_PREFIX",
|
||||
"APP_USER_NAME",
|
||||
"ATOMS_E2E_API_URL",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_ID",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_ID_BOOKER_EMBED",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_SECRET",
|
||||
"ATOMS_E2E_ORG_ID",
|
||||
"BASECAMP3_CLIENT_ID",
|
||||
"BASECAMP3_CLIENT_SECRET",
|
||||
"BASECAMP3_USER_AGENT",
|
||||
"BLACKLISTED_GUEST_EMAILS",
|
||||
"AUTH_BEARER_TOKEN_VERCEL",
|
||||
"BUILD_ID",
|
||||
"CAL_SIGNATURE_TOKEN",
|
||||
"CALCOM_PRIVATE_API_ROUTE",
|
||||
"CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY",
|
||||
"CALCOM_CREDENTIAL_SYNC_ENDPOINT",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET_HEADER_NAME",
|
||||
"CALCOM_ENV",
|
||||
"CALCOM_LICENSE_KEY",
|
||||
"CALCOM_QA_EMAIL",
|
||||
"CALCOM_QA_PASSWORD",
|
||||
"CALCOM_TELEMETRY_DISABLED",
|
||||
"CALCOM_CREDENTIAL_SYNC_HEADER_NAME",
|
||||
"CALENDSO_ENCRYPTION_KEY",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET",
|
||||
"CI",
|
||||
"CLOSECOM_CLIENT_ID",
|
||||
"CLOSECOM_CLIENT_SECRET",
|
||||
"CRON_API_KEY",
|
||||
"CRON_SECRET",
|
||||
"CRON_ENABLE_APP_SYNC",
|
||||
"CLOUDFLARE_TURNSTILE_SECRET",
|
||||
"DAILY_API_KEY",
|
||||
"DAILY_SCALE_PLAN",
|
||||
"DAILY_WEBHOOK_SECRET",
|
||||
"DAILY_MEETING_ENDED_WEBHOOK_SECRET",
|
||||
"DAILY_VIDEO_REGION",
|
||||
"DATABASE_CHUNK_SIZE",
|
||||
"DATABASE_DIRECT_URL",
|
||||
"DATABASE_URL",
|
||||
"DEBUG",
|
||||
"DUB_API_KEY",
|
||||
"NEXT_PUBLIC_DUB_PROGRAM_ID",
|
||||
"E2E_TEST_APPLE_CALENDAR_EMAIL",
|
||||
"E2E_TEST_APPLE_CALENDAR_PASSWORD",
|
||||
"E2E_TEST_CALCOM_QA_EMAIL",
|
||||
"E2E_TEST_CALCOM_QA_PASSWORD",
|
||||
"E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS",
|
||||
"E2E_TEST_CALCOM_GCAL_KEYS",
|
||||
"E2E_TEST_MAILHOG_ENABLED",
|
||||
"E2E_TEST_OIDC_CLIENT_ID",
|
||||
"E2E_TEST_OIDC_CLIENT_SECRET",
|
||||
"E2E_TEST_OIDC_PROVIDER_DOMAIN",
|
||||
"E2E_TEST_OIDC_USER_EMAIL",
|
||||
"E2E_TEST_OIDC_USER_PASSWORD",
|
||||
"E2E_TEST_SAML_ADMIN_EMAIL",
|
||||
"E2E_TEST_SAML_ADMIN_PASSWORD",
|
||||
"EMAIL_FROM",
|
||||
"EMAIL_FROM_NAME",
|
||||
"EMAIL_SERVER_HOST",
|
||||
"EMAIL_SERVER_PASSWORD",
|
||||
"EMAIL_SERVER_PORT",
|
||||
"EMAIL_SERVER_USER",
|
||||
"EMAIL_SERVER",
|
||||
"ENABLE_EXPANDED_CACHE",
|
||||
"EXCHANGE_DEFAULT_EWS_URL",
|
||||
"FORMBRICKS_FEEDBACK_SURVEY_ID",
|
||||
"AVATARAPI_USERNAME",
|
||||
"AVATARAPI_PASSWORD",
|
||||
"GIPHY_API_KEY",
|
||||
"GITHUB_API_REPO_TOKEN",
|
||||
"GOOGLE_API_CREDENTIALS",
|
||||
"GOOGLE_LOGIN_ENABLED",
|
||||
"GOOGLE_WEBHOOK_TOKEN",
|
||||
"HEROKU_APP_NAME",
|
||||
"HUBSPOT_CLIENT_ID",
|
||||
"HUBSPOT_CLIENT_SECRET",
|
||||
"IFFY_API_KEY",
|
||||
"INTEGRATION_TEST_MODE",
|
||||
"IS_E2E",
|
||||
"INTERCOM_SECRET",
|
||||
"INTERCOM_SECRET",
|
||||
"INSIGHTS_DATABASE_URL",
|
||||
"IP_BANLIST",
|
||||
"LARK_OPEN_APP_ID",
|
||||
"LARK_OPEN_APP_SECRET",
|
||||
"LARK_OPEN_VERIFICATION_TOKEN",
|
||||
"MOCK_PAYMENT_APP_ENABLED",
|
||||
"MS_GRAPH_CLIENT_ID",
|
||||
"MS_GRAPH_CLIENT_SECRET",
|
||||
"NEXT_PUBLIC_API_URL",
|
||||
"NEXT_PUBLIC_APP_NAME",
|
||||
"NEXT_PUBLIC_AUTH_URL",
|
||||
"NEXT_PUBLIC_CALCOM_VERSION",
|
||||
"NEXT_PUBLIC_COMPANY_NAME",
|
||||
"NEXT_PUBLIC_CONSOLE_URL",
|
||||
"NEXT_PUBLIC_LOGGER_LEVEL",
|
||||
"NEXT_PUBLIC_DISABLE_SIGNUP",
|
||||
"NEXT_PUBLIC_EMBED_LIB_URL",
|
||||
"NEXT_PUBLIC_FORMBRICKS_HOST_URL",
|
||||
"NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID",
|
||||
"NEXT_PUBLIC_HOSTED_CAL_FEATURES",
|
||||
"NEXT_PUBLIC_MINUTES_TO_BOOK",
|
||||
"NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED",
|
||||
"NEXT_PUBLIC_SENDER_ID",
|
||||
"NEXT_PUBLIC_SENDGRID_SENDER_NAME",
|
||||
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID",
|
||||
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_PUBLISHABLE_KEY",
|
||||
"NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS",
|
||||
"NEXT_PUBLIC_SWAGGER_DOCS_URL",
|
||||
"NEXT_PUBLIC_TEAM_IMPERSONATION",
|
||||
"NEXT_PUBLIC_VERCEL_URL",
|
||||
"NEXT_PUBLIC_CLOUDFLARE_SITEKEY",
|
||||
"NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER",
|
||||
"NEXT_PUBLIC_VERCEL_ENV",
|
||||
"NEXT_PUBLIC_VERCEL_BRANCH_URL",
|
||||
"NEXT_PUBLIC_GTM_ID",
|
||||
"NEXT_PUBLIC_ORGANIZATIONS_MIN_SELF_SERVE_SEATS",
|
||||
"NEXT_PUBLIC_ORGANIZATIONS_SELF_SERVE_PRICE_NEW",
|
||||
"NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL",
|
||||
"NEXT_PUBLIC_WEBSITE_TERMS_URL",
|
||||
"NEXT_RUNTIME",
|
||||
"NEXTAUTH_COOKIE_DOMAIN",
|
||||
"NEXTAUTH_SECRET",
|
||||
"NEXTAUTH_URL",
|
||||
"NODE_ENV",
|
||||
"ORGANIZATIONS_ENABLED",
|
||||
"ORGANIZATIONS_AUTOLINK",
|
||||
"PAYMENT_FEE_FIXED",
|
||||
"PAYMENT_FEE_PERCENTAGE",
|
||||
"PLAYWRIGHT_HEADLESS",
|
||||
"PLAYWRIGHT_TEST_BASE_URL",
|
||||
"PRISMA_FIELD_ENCRYPTION_KEY",
|
||||
"PRISMA_GENERATE_DATAPROXY",
|
||||
"PROJECT_ID_VERCEL",
|
||||
"QUICK",
|
||||
"RAILWAY_STATIC_URL",
|
||||
"RENDER_EXTERNAL_URL",
|
||||
"RESERVED_SUBDOMAINS",
|
||||
"RETELL_AI_KEY",
|
||||
"SALESFORCE_CONSUMER_KEY",
|
||||
"SALESFORCE_CONSUMER_SECRET",
|
||||
"SAML_ADMINS",
|
||||
"SAML_CLIENT_SECRET_VERIFIER",
|
||||
"SAML_DATABASE_URL",
|
||||
"SEND_FEEDBACK_EMAIL",
|
||||
"SENDGRID_API_KEY",
|
||||
"SENDGRID_EMAIL",
|
||||
"SENDGRID_SYNC_API_KEY",
|
||||
"SKIP_DB_MIGRATIONS",
|
||||
"SLACK_CLIENT_ID",
|
||||
"SLACK_CLIENT_SECRET",
|
||||
"SLACK_SIGNING_SECRET",
|
||||
"STRIPE_CLIENT_ID",
|
||||
"STRIPE_PRIVATE_KEY",
|
||||
"STRIPE_PRODUCT_ID_ESSENTIALS",
|
||||
"STRIPE_PRODUCT_ID_SCALE",
|
||||
"STRIPE_PRODUCT_ID_STARTER",
|
||||
"STRIPE_WEBHOOK_SECRET",
|
||||
"STRIPE_WEBHOOK_SECRET_APPS",
|
||||
"STRIPE_WEBHOOK_SECRET_BILLING",
|
||||
"STRIPE_TEAM_MONTHLY_PRICE_ID",
|
||||
"TANDEM_BASE_URL",
|
||||
"TANDEM_CLIENT_ID",
|
||||
"TANDEM_CLIENT_SECRET",
|
||||
"TASKER_ENABLE_WEBHOOKS",
|
||||
"TEAM_ID_VERCEL",
|
||||
"TELEMETRY_DEBUG",
|
||||
"TWILIO_MESSAGING_SID",
|
||||
"TWILIO_OPT_OUT_ENABLED",
|
||||
"TWILIO_PHONE_NUMBER",
|
||||
"TWILIO_WHATSAPP_PHONE_NUMBER",
|
||||
"TWILIO_SID",
|
||||
"TWILIO_TOKEN",
|
||||
"TWILIO_VERIFY_SID",
|
||||
"TWILIO_WHATSAPP_REMINDER_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_CANCELLED_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_COMPLETED_CONTENT_SID",
|
||||
"UPSTASH_REDIS_REST_TOKEN",
|
||||
"UPSTASH_REDIS_REST_URL",
|
||||
"UNKEY_ROOT_KEY",
|
||||
"USERNAME_BLACKLIST_URL",
|
||||
"VERCEL_ENV",
|
||||
"VERCEL_URL",
|
||||
"VITAL_API_KEY",
|
||||
"VITAL_DEVELOPMENT_MODE",
|
||||
"VITAL_REGION",
|
||||
"VITAL_WEBHOOK_SECRET",
|
||||
"ZAPIER_INVITE_LINK",
|
||||
"ZOHOCRM_CLIENT_ID",
|
||||
"ZOHOCRM_CLIENT_SECRET",
|
||||
"ZOOM_CLIENT_ID",
|
||||
"ZOOM_CLIENT_SECRET",
|
||||
"REVERT_API_KEY",
|
||||
"REVERT_API_URL",
|
||||
"REVERT_PUBLIC_TOKEN",
|
||||
"RESEND_API_KEY",
|
||||
"LOCAL_TESTING_DOMAIN_VERCEL",
|
||||
"AUTH_BEARER_TOKEN_CLOUDFLARE",
|
||||
"CLOUDFLARE_ZONE_ID",
|
||||
"NEXT_PUBLIC_MINTLIFY_CHAT_API_KEY",
|
||||
"NEXT_PUBLIC_CHAT_API_URL",
|
||||
"NEXT_PUBLIC_DOCS_URL",
|
||||
"CLOUDFLARE_VERCEL_CNAME",
|
||||
"CLOUDFLARE_DNS",
|
||||
"EMBED_PUBLIC_EMBED_FINGER_PRINT",
|
||||
"EMBED_PUBLIC_EMBED_VERSION",
|
||||
"EMBED_PUBLIC_WEBAPP_URL",
|
||||
"EMBED_PUBLIC_VERCEL_URL",
|
||||
"EMBED_PUBLIC_EMBED_LIB_URL",
|
||||
"NEXT_PUBLIC_ENABLE_PROFILE_SWITCHER",
|
||||
"NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS",
|
||||
"NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS",
|
||||
"NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS",
|
||||
"NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM",
|
||||
"NEXT_PUBLIC_QUICK_AVAILABILITY_ROLLOUT",
|
||||
"NEXT_PUBLIC_HEAD_SCRIPTS",
|
||||
"NEXT_PUBLIC_BODY_SCRIPTS",
|
||||
"NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED",
|
||||
"NEXT_PUBLIC_API_V2_ROOT_URL",
|
||||
"NEXT_PUBLIC_VAPID_PUBLIC_KEY",
|
||||
"VAPID_PRIVATE_KEY",
|
||||
"CAL_VIDEO_BUCKET_NAME",
|
||||
"CAL_VIDEO_BUCKET_REGION",
|
||||
"CAL_VIDEO_ASSUME_ROLE_ARN",
|
||||
"CAL_VIDEO_MEETING_LINK_FOR_TESTING",
|
||||
"NEXT_PUBLIC_POSTHOG_KEY",
|
||||
"NEXT_PUBLIC_POSTHOG_HOST",
|
||||
"VAPID_PRIVATE_KEY",
|
||||
"HUDDLE01_API_TOKEN",
|
||||
"LINGO_DOT_DEV_API_KEY",
|
||||
"DIRECTORY_IDS_TO_LOG",
|
||||
"NEXT_PUBLIC_SINGLE_ORG_SLUG",
|
||||
"GOOGLE_REFRESH_TOKEN",
|
||||
"GOOGLE_CLIENT_ID",
|
||||
"GOOGLE_CLIENT_SECRET",
|
||||
"ZOOM_REFRESH_TOKEN",
|
||||
"CALCOM_ADMIN_API_KEY",
|
||||
"NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED",
|
||||
"CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY",
|
||||
"CAL_VIDEO_RECORDING_TOKEN_SECRET",
|
||||
"ORGANIZER_EMAIL_EXEMPT_DOMAINS",
|
||||
"SLOTS_CACHE_TTL"
|
||||
],
|
||||
"tasks": {
|
||||
"@calcom/prisma#build": {
|
||||
"cache": false,
|
||||
"dependsOn": ["post-install"]
|
||||
@@ -207,7 +458,7 @@
|
||||
},
|
||||
"@calcom/app-store-cli#build": {
|
||||
"cache": false,
|
||||
"inputs": ["../../app-store/**/**"],
|
||||
"inputs": ["../../app-store/**"],
|
||||
"outputs": ["../../app-store/apps.server.generated.ts", "../../app-store/apps.browser.generated.tsx"]
|
||||
},
|
||||
"@calcom/embed-react#type-check": {
|
||||
@@ -247,255 +498,5 @@
|
||||
"//#test": {
|
||||
"cache": false
|
||||
}
|
||||
},
|
||||
"globalDependencies": ["yarn.lock"],
|
||||
"globalEnv": [
|
||||
"ALLOWED_HOSTNAMES",
|
||||
"ANALYZE",
|
||||
"API_KEY_PREFIX",
|
||||
"APP_USER_NAME",
|
||||
"ATOMS_E2E_API_URL",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_ID",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_ID_BOOKER_EMBED",
|
||||
"ATOMS_E2E_OAUTH_CLIENT_SECRET",
|
||||
"ATOMS_E2E_ORG_ID",
|
||||
"BASECAMP3_CLIENT_ID",
|
||||
"BASECAMP3_CLIENT_SECRET",
|
||||
"BASECAMP3_USER_AGENT",
|
||||
"BLACKLISTED_GUEST_EMAILS",
|
||||
"AUTH_BEARER_TOKEN_VERCEL",
|
||||
"BUILD_ID",
|
||||
"CAL_SIGNATURE_TOKEN",
|
||||
"CALCOM_PRIVATE_API_ROUTE",
|
||||
"CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY",
|
||||
"CALCOM_CREDENTIAL_SYNC_ENDPOINT",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET_HEADER_NAME",
|
||||
"CALCOM_ENV",
|
||||
"CALCOM_LICENSE_KEY",
|
||||
"CALCOM_QA_EMAIL",
|
||||
"CALCOM_QA_PASSWORD",
|
||||
"CALCOM_TELEMETRY_DISABLED",
|
||||
"CALCOM_CREDENTIAL_SYNC_HEADER_NAME",
|
||||
"CALENDSO_ENCRYPTION_KEY",
|
||||
"CALCOM_CREDENTIAL_SYNC_SECRET",
|
||||
"CI",
|
||||
"CLOSECOM_CLIENT_ID",
|
||||
"CLOSECOM_CLIENT_SECRET",
|
||||
"CRON_API_KEY",
|
||||
"CRON_SECRET",
|
||||
"CRON_ENABLE_APP_SYNC",
|
||||
"CLOUDFLARE_TURNSTILE_SECRET",
|
||||
"DAILY_API_KEY",
|
||||
"DAILY_SCALE_PLAN",
|
||||
"DAILY_WEBHOOK_SECRET",
|
||||
"DAILY_MEETING_ENDED_WEBHOOK_SECRET",
|
||||
"DAILY_VIDEO_REGION",
|
||||
"DATABASE_CHUNK_SIZE",
|
||||
"DATABASE_DIRECT_URL",
|
||||
"DATABASE_URL",
|
||||
"DEBUG",
|
||||
"DUB_API_KEY",
|
||||
"NEXT_PUBLIC_DUB_PROGRAM_ID",
|
||||
"E2E_TEST_APPLE_CALENDAR_EMAIL",
|
||||
"E2E_TEST_APPLE_CALENDAR_PASSWORD",
|
||||
"E2E_TEST_CALCOM_QA_EMAIL",
|
||||
"E2E_TEST_CALCOM_QA_PASSWORD",
|
||||
"E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS",
|
||||
"E2E_TEST_CALCOM_GCAL_KEYS",
|
||||
"E2E_TEST_MAILHOG_ENABLED",
|
||||
"E2E_TEST_OIDC_CLIENT_ID",
|
||||
"E2E_TEST_OIDC_CLIENT_SECRET",
|
||||
"E2E_TEST_OIDC_PROVIDER_DOMAIN",
|
||||
"E2E_TEST_OIDC_USER_EMAIL",
|
||||
"E2E_TEST_OIDC_USER_PASSWORD",
|
||||
"E2E_TEST_SAML_ADMIN_EMAIL",
|
||||
"E2E_TEST_SAML_ADMIN_PASSWORD",
|
||||
"EMAIL_FROM",
|
||||
"EMAIL_FROM_NAME",
|
||||
"EMAIL_SERVER_HOST",
|
||||
"EMAIL_SERVER_PASSWORD",
|
||||
"EMAIL_SERVER_PORT",
|
||||
"EMAIL_SERVER_USER",
|
||||
"EMAIL_SERVER",
|
||||
"ENABLE_EXPANDED_CACHE",
|
||||
"EXCHANGE_DEFAULT_EWS_URL",
|
||||
"FORMBRICKS_FEEDBACK_SURVEY_ID",
|
||||
"AVATARAPI_USERNAME",
|
||||
"AVATARAPI_PASSWORD",
|
||||
"GIPHY_API_KEY",
|
||||
"GITHUB_API_REPO_TOKEN",
|
||||
"GOOGLE_API_CREDENTIALS",
|
||||
"GOOGLE_LOGIN_ENABLED",
|
||||
"GOOGLE_WEBHOOK_TOKEN",
|
||||
"HEROKU_APP_NAME",
|
||||
"HUBSPOT_CLIENT_ID",
|
||||
"HUBSPOT_CLIENT_SECRET",
|
||||
"IFFY_API_KEY",
|
||||
"INTEGRATION_TEST_MODE",
|
||||
"IS_E2E",
|
||||
"INTERCOM_SECRET",
|
||||
"INTERCOM_SECRET",
|
||||
"INSIGHTS_DATABASE_URL",
|
||||
"IP_BANLIST",
|
||||
"LARK_OPEN_APP_ID",
|
||||
"LARK_OPEN_APP_SECRET",
|
||||
"LARK_OPEN_VERIFICATION_TOKEN",
|
||||
"MOCK_PAYMENT_APP_ENABLED",
|
||||
"MS_GRAPH_CLIENT_ID",
|
||||
"MS_GRAPH_CLIENT_SECRET",
|
||||
"NEXT_PUBLIC_API_URL",
|
||||
"NEXT_PUBLIC_APP_NAME",
|
||||
"NEXT_PUBLIC_AUTH_URL",
|
||||
"NEXT_PUBLIC_CALCOM_VERSION",
|
||||
"NEXT_PUBLIC_COMPANY_NAME",
|
||||
"NEXT_PUBLIC_CONSOLE_URL",
|
||||
"NEXT_PUBLIC_LOGGER_LEVEL",
|
||||
"NEXT_PUBLIC_DISABLE_SIGNUP",
|
||||
"NEXT_PUBLIC_EMBED_LIB_URL",
|
||||
"NEXT_PUBLIC_FORMBRICKS_HOST_URL",
|
||||
"NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID",
|
||||
"NEXT_PUBLIC_HOSTED_CAL_FEATURES",
|
||||
"NEXT_PUBLIC_MINUTES_TO_BOOK",
|
||||
"NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED",
|
||||
"NEXT_PUBLIC_SENDER_ID",
|
||||
"NEXT_PUBLIC_SENDGRID_SENDER_NAME",
|
||||
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID",
|
||||
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_PUBLISHABLE_KEY",
|
||||
"NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS",
|
||||
"NEXT_PUBLIC_SWAGGER_DOCS_URL",
|
||||
"NEXT_PUBLIC_TEAM_IMPERSONATION",
|
||||
"NEXT_PUBLIC_VERCEL_URL",
|
||||
"NEXT_PUBLIC_CLOUDFLARE_SITEKEY",
|
||||
"NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER",
|
||||
"NEXT_PUBLIC_VERCEL_ENV",
|
||||
"NEXT_PUBLIC_VERCEL_BRANCH_URL",
|
||||
"NEXT_PUBLIC_GTM_ID",
|
||||
"NEXT_PUBLIC_ORGANIZATIONS_MIN_SELF_SERVE_SEATS",
|
||||
"NEXT_PUBLIC_ORGANIZATIONS_SELF_SERVE_PRICE_NEW",
|
||||
"NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL",
|
||||
"NEXT_PUBLIC_WEBSITE_TERMS_URL",
|
||||
"NEXT_RUNTIME",
|
||||
"NEXTAUTH_COOKIE_DOMAIN",
|
||||
"NEXTAUTH_SECRET",
|
||||
"NEXTAUTH_URL",
|
||||
"NODE_ENV",
|
||||
"ORGANIZATIONS_ENABLED",
|
||||
"ORGANIZATIONS_AUTOLINK",
|
||||
"PAYMENT_FEE_FIXED",
|
||||
"PAYMENT_FEE_PERCENTAGE",
|
||||
"PLAYWRIGHT_HEADLESS",
|
||||
"PLAYWRIGHT_TEST_BASE_URL",
|
||||
"PRISMA_FIELD_ENCRYPTION_KEY",
|
||||
"PRISMA_GENERATE_DATAPROXY",
|
||||
"PROJECT_ID_VERCEL",
|
||||
"QUICK",
|
||||
"RAILWAY_STATIC_URL",
|
||||
"RENDER_EXTERNAL_URL",
|
||||
"RESERVED_SUBDOMAINS",
|
||||
"RETELL_AI_KEY",
|
||||
"SALESFORCE_CONSUMER_KEY",
|
||||
"SALESFORCE_CONSUMER_SECRET",
|
||||
"SAML_ADMINS",
|
||||
"SAML_CLIENT_SECRET_VERIFIER",
|
||||
"SAML_DATABASE_URL",
|
||||
"SEND_FEEDBACK_EMAIL",
|
||||
"SENDGRID_API_KEY",
|
||||
"SENDGRID_EMAIL",
|
||||
"SENDGRID_SYNC_API_KEY",
|
||||
"SKIP_DB_MIGRATIONS",
|
||||
"SLACK_CLIENT_ID",
|
||||
"SLACK_CLIENT_SECRET",
|
||||
"SLACK_SIGNING_SECRET",
|
||||
"STRIPE_CLIENT_ID",
|
||||
"STRIPE_PRIVATE_KEY",
|
||||
"STRIPE_PRODUCT_ID_ESSENTIALS",
|
||||
"STRIPE_PRODUCT_ID_SCALE",
|
||||
"STRIPE_PRODUCT_ID_STARTER",
|
||||
"STRIPE_WEBHOOK_SECRET",
|
||||
"STRIPE_WEBHOOK_SECRET_APPS",
|
||||
"STRIPE_WEBHOOK_SECRET_BILLING",
|
||||
"TANDEM_BASE_URL",
|
||||
"TANDEM_CLIENT_ID",
|
||||
"TANDEM_CLIENT_SECRET",
|
||||
"TASKER_ENABLE_WEBHOOKS",
|
||||
"TEAM_ID_VERCEL",
|
||||
"TELEMETRY_DEBUG",
|
||||
"TWILIO_MESSAGING_SID",
|
||||
"TWILIO_OPT_OUT_ENABLED",
|
||||
"TWILIO_PHONE_NUMBER",
|
||||
"TWILIO_WHATSAPP_PHONE_NUMBER",
|
||||
"TWILIO_SID",
|
||||
"TWILIO_TOKEN",
|
||||
"TWILIO_VERIFY_SID",
|
||||
"TWILIO_WHATSAPP_REMINDER_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_CANCELLED_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID",
|
||||
"TWILIO_WHATSAPP_COMPLETED_CONTENT_SID",
|
||||
"UPSTASH_REDIS_REST_TOKEN",
|
||||
"UPSTASH_REDIS_REST_URL",
|
||||
"UNKEY_ROOT_KEY",
|
||||
"USERNAME_BLACKLIST_URL",
|
||||
"VERCEL_ENV",
|
||||
"VERCEL_URL",
|
||||
"VITAL_API_KEY",
|
||||
"VITAL_DEVELOPMENT_MODE",
|
||||
"VITAL_REGION",
|
||||
"VITAL_WEBHOOK_SECRET",
|
||||
"ZAPIER_INVITE_LINK",
|
||||
"ZOHOCRM_CLIENT_ID",
|
||||
"ZOHOCRM_CLIENT_SECRET",
|
||||
"ZOOM_CLIENT_ID",
|
||||
"ZOOM_CLIENT_SECRET",
|
||||
"REVERT_API_KEY",
|
||||
"REVERT_API_URL",
|
||||
"REVERT_PUBLIC_TOKEN",
|
||||
"RESEND_API_KEY",
|
||||
"LOCAL_TESTING_DOMAIN_VERCEL",
|
||||
"AUTH_BEARER_TOKEN_CLOUDFLARE",
|
||||
"CLOUDFLARE_ZONE_ID",
|
||||
"NEXT_PUBLIC_MINTLIFY_CHAT_API_KEY",
|
||||
"NEXT_PUBLIC_CHAT_API_URL",
|
||||
"NEXT_PUBLIC_DOCS_URL",
|
||||
"CLOUDFLARE_VERCEL_CNAME",
|
||||
"CLOUDFLARE_DNS",
|
||||
"EMBED_PUBLIC_EMBED_FINGER_PRINT",
|
||||
"EMBED_PUBLIC_EMBED_VERSION",
|
||||
"EMBED_PUBLIC_WEBAPP_URL",
|
||||
"EMBED_PUBLIC_VERCEL_URL",
|
||||
"EMBED_PUBLIC_EMBED_LIB_URL",
|
||||
"NEXT_PUBLIC_ENABLE_PROFILE_SWITCHER",
|
||||
"NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS",
|
||||
"NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS",
|
||||
"NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS",
|
||||
"NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM",
|
||||
"NEXT_PUBLIC_QUICK_AVAILABILITY_ROLLOUT",
|
||||
"NEXT_PUBLIC_HEAD_SCRIPTS",
|
||||
"NEXT_PUBLIC_BODY_SCRIPTS",
|
||||
"NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED",
|
||||
"NEXT_PUBLIC_API_V2_ROOT_URL",
|
||||
"NEXT_PUBLIC_VAPID_PUBLIC_KEY",
|
||||
"VAPID_PRIVATE_KEY",
|
||||
"CAL_VIDEO_BUCKET_NAME",
|
||||
"CAL_VIDEO_BUCKET_REGION",
|
||||
"CAL_VIDEO_ASSUME_ROLE_ARN",
|
||||
"CAL_VIDEO_MEETING_LINK_FOR_TESTING",
|
||||
"NEXT_PUBLIC_POSTHOG_KEY",
|
||||
"NEXT_PUBLIC_POSTHOG_HOST",
|
||||
"VAPID_PRIVATE_KEY",
|
||||
"HUDDLE01_API_TOKEN",
|
||||
"LINGO_DOT_DEV_API_KEY",
|
||||
"DIRECTORY_IDS_TO_LOG",
|
||||
"NEXT_PUBLIC_SINGLE_ORG_SLUG",
|
||||
"GOOGLE_REFRESH_TOKEN",
|
||||
"GOOGLE_CLIENT_ID",
|
||||
"GOOGLE_CLIENT_SECRET",
|
||||
"ZOOM_REFRESH_TOKEN",
|
||||
"CALCOM_ADMIN_API_KEY",
|
||||
"NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED",
|
||||
"CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY",
|
||||
"CAL_VIDEO_RECORDING_TOKEN_SECRET",
|
||||
"ORGANIZER_EMAIL_EXEMPT_DOMAINS",
|
||||
"SLOTS_CACHE_TTL"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2500,7 +2500,7 @@ __metadata:
|
||||
"@axiomhq/winston": ^1.2.0
|
||||
"@calcom/platform-constants": "*"
|
||||
"@calcom/platform-enums": "*"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.312"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.314"
|
||||
"@calcom/platform-types": "*"
|
||||
"@calcom/platform-utils": "*"
|
||||
"@calcom/prisma": "*"
|
||||
@@ -3559,13 +3559,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.312":
|
||||
version: 0.0.312
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.312"
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.314":
|
||||
version: 0.0.314
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.314"
|
||||
dependencies:
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
checksum: 46520737404a61f3aa96c6f1adfa81e67f645fd984f495662ca7c63184b0310117a1dad57ddbe7ea487f2e5a0c9cbe62749a9a43b3197a021b19e3ccadc1f5b2
|
||||
checksum: b59eb591273334ed7d7d07a8f0518eb8cb40255883dff49626f85d3bd804db4da357bf77cd4f7b14ae0743d0a699647232b39956af9123bdc5e7b2458cab30eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -21694,7 +21694,7 @@ __metadata:
|
||||
prismock: ^1.33.4
|
||||
resize-observer-polyfill: ^1.5.1
|
||||
tsc-absolute: ^1.0.0
|
||||
turbo: ^1.10.1
|
||||
turbo: ^2.5.5
|
||||
typescript: 5.9.0-beta
|
||||
vitest: ^2.1.1
|
||||
vitest-fetch-mock: ^0.3.0
|
||||
@@ -44926,58 +44926,58 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-darwin-64@npm:1.10.1"
|
||||
"turbo-darwin-64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-darwin-64@npm:2.5.5"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-darwin-arm64@npm:1.10.1"
|
||||
"turbo-darwin-arm64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-darwin-arm64@npm:2.5.5"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-linux-64@npm:1.10.1"
|
||||
"turbo-linux-64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-linux-64@npm:2.5.5"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-linux-arm64@npm:1.10.1"
|
||||
"turbo-linux-arm64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-linux-arm64@npm:2.5.5"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-windows-64@npm:1.10.1"
|
||||
"turbo-windows-64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-windows-64@npm:2.5.5"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-windows-arm64@npm:1.10.1"
|
||||
"turbo-windows-arm64@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo-windows-arm64@npm:2.5.5"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo@npm:^1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo@npm:1.10.1"
|
||||
"turbo@npm:^2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "turbo@npm:2.5.5"
|
||||
dependencies:
|
||||
turbo-darwin-64: 1.10.1
|
||||
turbo-darwin-arm64: 1.10.1
|
||||
turbo-linux-64: 1.10.1
|
||||
turbo-linux-arm64: 1.10.1
|
||||
turbo-windows-64: 1.10.1
|
||||
turbo-windows-arm64: 1.10.1
|
||||
turbo-darwin-64: 2.5.5
|
||||
turbo-darwin-arm64: 2.5.5
|
||||
turbo-linux-64: 2.5.5
|
||||
turbo-linux-arm64: 2.5.5
|
||||
turbo-windows-64: 2.5.5
|
||||
turbo-windows-arm64: 2.5.5
|
||||
dependenciesMeta:
|
||||
turbo-darwin-64:
|
||||
optional: true
|
||||
@@ -44993,7 +44993,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
turbo: bin/turbo
|
||||
checksum: 3551c57be67e833583de549d74d5ec991e0f1e38047e25600b8acc085a1a8a4007992259a015eb9f2c0abd68156545800bd34e7b5ad337d1998b32461952d3fb
|
||||
checksum: 8da6525097ba17bb59571eb512f216362b046bbf88765ddb05fb9937a44e04eeba3bb883c55fca53a7e8a104e02366b4f6f99ef90d12078d0dc95d4659004af6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user