feat: add advanced tab attributes to api/v2/event-types (#16314)
* added bookerLayouts * added requiresConfirmation * added tests * undo extra changes * undo extra change * fixed imports * added requiresBookerEmailVerification * added hideCalendarNotes * added lockTimeZoneToggleOnBookingPage * added eventTypeColor * undo extra changes * added seats * added requiresConfirmationWillBlockSlot to requiresConfirmation * refactor: Implement BaseEventType class to avoid duplication across EventType input/output classes ## D-R-Y ## * temp-fix: will revert later * Correct placement of users array from EventTypeOutput to TeamEventTypeOutput * Refactor: Shifted transformation logic to controllers using interceptors and pipes for a cleaner service layer. * small fix * Refactor: Moved transformation logic from service to controller using interceptors and pipes ** organisations/event-types * feat: adde tests for `validateEventTypeInputs` * fix: failing test * fix: layout validator * renamed blockCalendarForUnconfirmedBookings to blockUnconfirmedBookingsInBooker * testing changes * added disabled state * fix merege conflict * renamed requiresConfirmation to confirmationPolicy * renamed OutputEventTypesResponseInterceptor to OutputTeamEventTypesResponseInterceptor * renamed `darkThemeColor` -> `darkThemeHex` and `lightThemeColor` -> `lightThemeHex` * renamed `eventTypeColor` -> `color` * changed `requiresConfirmation` -> `confirmationPolicy` * added tests for disabled state * added class-validators to `disabled?: false` * Revert: Split create and update input/output classes * feat: Refactor responses to use DTOs with plainToClass for explicit property control • Updated all endpoints to return response DTOs (e.g., CreateTeamEventTypeOutput, GetTeamEventTypeOutput) to ensure consistent Swagger documentation generation. • Introduced plainToClass for transforming responses, enabling explicit control over which properties are returned in the response, following Morgan’s recommendation. • Replaced generic HandlerResponse type with class-validator based DTOs for each endpoint, ensuring TypeScript enforces correct response structure. • Applied strategy: "excludeAll" to limit response properties to only those explicitly defined in the DTO, preventing unintended fields from being included in the API responses. * added pipes to transform output data * add clean script to platform-types package.json * added customName * added destination calendar * fixed type errors * added useDestinationCalendarEmail * refactor: api-reqest and api-response * Update event-type.output.ts * fixed errors * Update yarn.lock * added some missed properties to output * Update constants.ts * removed return type * fixed some type errors * reuse types * Improve readability of validation functions * Update CHANGELOG.md * chore: post publish platform libraries * Update documentation.json * fix: reset platform libraries to 0.0.0 * fixup! fix: reset platform libraries to 0.0.0 * Update event-type.tranformed.ts --------- Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
co-authored by
supalarry
Morgan Vernay
parent
6de454b071
commit
2e2a6c73b1
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.37",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.39",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -198,7 +198,7 @@ class DestinationCalendar {
|
||||
integrationTitle?: string;
|
||||
}
|
||||
|
||||
class ConnectedCalendarsData {
|
||||
export class ConnectedCalendarsData {
|
||||
@ValidateNested({ each: true })
|
||||
@IsArray()
|
||||
connectedCalendars!: ConnectedCalendar[];
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
CredentialsRepository,
|
||||
CredentialsWithUserEmail,
|
||||
} from "@/modules/credentials/credentials.repository";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
@@ -14,7 +13,6 @@ import {
|
||||
UnauthorizedException,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { User } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { DateTime } from "luxon";
|
||||
@@ -34,9 +32,7 @@ export class CalendarsService {
|
||||
private readonly credentialsRepository: CredentialsRepository,
|
||||
private readonly appsRepository: AppsRepository,
|
||||
private readonly calendarsRepository: CalendarsRepository,
|
||||
private readonly dbRead: PrismaReadService,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly selectedCalendarsRepository: SelectedCalendarsRepository
|
||||
) {}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export class GoogleCalendarService implements OAuthCalendarApp {
|
||||
): Promise<{ status: typeof SUCCESS_STATUS; data: { authUrl: string } }> {
|
||||
const accessToken = authorization.replace("Bearer ", "");
|
||||
const origin = req.get("origin") ?? req.get("host");
|
||||
const redirectUrl = await await this.getCalendarRedirectUrl(accessToken, origin ?? "", redir);
|
||||
const redirectUrl = await this.getCalendarRedirectUrl(accessToken, origin ?? "", redir);
|
||||
|
||||
return { status: SUCCESS_STATUS, data: { authUrl: redirectUrl } };
|
||||
}
|
||||
|
||||
@@ -1,16 +1,44 @@
|
||||
import { IntegrationLocation } from "@calcom/lib";
|
||||
|
||||
type BaseEventType = {
|
||||
length: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type EventTypeWithLocation = BaseEventType & {
|
||||
locations: IntegrationLocation[];
|
||||
};
|
||||
|
||||
const thirtyMinutes: BaseEventType = {
|
||||
length: 30,
|
||||
slug: "thirty-minutes",
|
||||
title: "30 Minutes",
|
||||
};
|
||||
|
||||
const thirtyMinutesVideo: EventTypeWithLocation = {
|
||||
length: 30,
|
||||
slug: "thirty-minutes-video",
|
||||
title: "30 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
};
|
||||
|
||||
const sixtyMinutes: BaseEventType = {
|
||||
length: 60,
|
||||
slug: "sixty-minutes",
|
||||
title: "60 Minutes",
|
||||
};
|
||||
|
||||
const sixtyMinutesVideo: EventTypeWithLocation = {
|
||||
length: 60,
|
||||
slug: "sixty-minutes-video",
|
||||
title: "60 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
};
|
||||
|
||||
export const DEFAULT_EVENT_TYPES = {
|
||||
thirtyMinutes: { length: 30, slug: "thirty-minutes", title: "30 Minutes" },
|
||||
thirtyMinutesVideo: {
|
||||
length: 30,
|
||||
slug: "thirty-minutes-video",
|
||||
title: "30 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
},
|
||||
sixtyMinutes: { length: 60, slug: "sixty-minutes", title: "60 Minutes" },
|
||||
sixtyMinutesVideo: {
|
||||
length: 60,
|
||||
slug: "sixty-minutes-video",
|
||||
title: "60 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
},
|
||||
thirtyMinutes,
|
||||
thirtyMinutesVideo,
|
||||
sixtyMinutes,
|
||||
sixtyMinutesVideo,
|
||||
};
|
||||
|
||||
+416
-32
@@ -20,7 +20,13 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import { BookingWindowPeriodInputTypeEnum_2024_06_14, FrequencyInput } from "@calcom/platform-enums";
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
FrequencyInput,
|
||||
} from "@calcom/platform-enums";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
@@ -230,11 +236,36 @@ describe("Event types Endpoints", () => {
|
||||
value: 30,
|
||||
rolling: true,
|
||||
},
|
||||
bookerLayouts: {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
},
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
},
|
||||
recurrence: {
|
||||
frequency: FrequencyInput.weekly,
|
||||
interval: 2,
|
||||
occurrences: 10,
|
||||
disabled: false,
|
||||
},
|
||||
requiresBookerEmailVerification: false,
|
||||
hideCalendarNotes: false,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
},
|
||||
customName: `{Event type title} between {Organiser} and {Scheduler}`,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -257,7 +288,19 @@ describe("Event types Endpoints", () => {
|
||||
expect(createdEventType.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
|
||||
expect(createdEventType.offsetStart).toEqual(body.offsetStart);
|
||||
expect(createdEventType.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(createdEventType.bookerLayouts).toEqual(body.bookerLayouts);
|
||||
expect(createdEventType.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(createdEventType.recurrence).toEqual(body.recurrence);
|
||||
expect(createdEventType.customName).toEqual(body.customName);
|
||||
expect(createdEventType.requiresBookerEmailVerification).toEqual(
|
||||
body.requiresBookerEmailVerification
|
||||
);
|
||||
|
||||
expect(createdEventType.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(createdEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
body.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(createdEventType.color).toEqual(body.color);
|
||||
|
||||
const responseBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
@@ -272,6 +315,317 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it(`/GET/even-types by username`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.[0];
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType?.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
expect(fetchedEventType.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType.requiresBookerEmailVerification).toEqual(
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType.color).toEqual(eventType.color);
|
||||
});
|
||||
|
||||
it("should return an error when creating an event type with seats enabled and multiple locations", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 2",
|
||||
slug: "coding-class-2",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
phone: "+37120993151",
|
||||
public: true,
|
||||
},
|
||||
],
|
||||
scheduleId: firstSchedule.id,
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when trying to enable seats for an event type with multiple locations", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 3",
|
||||
slug: "coding-class-3",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
phone: "+37120993151",
|
||||
public: true,
|
||||
},
|
||||
],
|
||||
scheduleId: firstSchedule.id,
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
lengthInMinutes: body.lengthInMinutes,
|
||||
locations: body.locations,
|
||||
scheduleId: firstSchedule.id,
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when creating an event type with seats enabled and confirmationPolicy enabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 4",
|
||||
slug: "coding-class-4",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
scheduleId: firstSchedule.id,
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: false,
|
||||
},
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when trying to enable seats for an event type with confirmationPolicy enabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 5",
|
||||
slug: "coding-class-5",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: false,
|
||||
},
|
||||
scheduleId: firstSchedule.id,
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
console.log("createdEventType: ", createdEventType);
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
lengthInMinutes: body.lengthInMinutes,
|
||||
confirmationPolicy: body.confirmationPolicy,
|
||||
scheduleId: firstSchedule.id,
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when trying to set multiple locations for an event type with seats enabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 6",
|
||||
slug: "coding-class-6",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
scheduleId: firstSchedule.id,
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
lengthInMinutes: body.lengthInMinutes,
|
||||
seats: body.seats,
|
||||
scheduleId: firstSchedule.id,
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
phone: "+37120993151",
|
||||
public: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when creating an event type with confirmationPolicy enabled and seats enabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 7",
|
||||
slug: "coding-class-7",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: false,
|
||||
},
|
||||
scheduleId: firstSchedule.id,
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return an error when trying to enable confirmationPolicy for an event type with seats enabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class 8",
|
||||
slug: "coding-class-8",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
scheduleId: firstSchedule.id,
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
lengthInMinutes: body.lengthInMinutes,
|
||||
seats: body.seats,
|
||||
scheduleId: firstSchedule.id,
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: false,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should update event type", async () => {
|
||||
const newTitle = "Coding class in Italian!";
|
||||
|
||||
@@ -293,11 +647,32 @@ describe("Event types Endpoints", () => {
|
||||
value: 40,
|
||||
rolling: false,
|
||||
},
|
||||
bookerLayouts: {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
},
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: false,
|
||||
},
|
||||
recurrence: {
|
||||
frequency: FrequencyInput.monthly,
|
||||
interval: 4,
|
||||
occurrences: 10,
|
||||
disabled: false,
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
},
|
||||
customName: `{Event type title} betweennnnnnnnnnn {Organiser} and {Scheduler}`,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -323,7 +698,18 @@ describe("Event types Endpoints", () => {
|
||||
expect(updatedEventType.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
|
||||
expect(updatedEventType.offsetStart).toEqual(body.offsetStart);
|
||||
expect(updatedEventType.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(updatedEventType.bookerLayouts).toEqual(body.bookerLayouts);
|
||||
expect(updatedEventType.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(updatedEventType.recurrence).toEqual(body.recurrence);
|
||||
expect(updatedEventType.customName).toEqual(body.customName);
|
||||
expect(updatedEventType.requiresBookerEmailVerification).toEqual(
|
||||
body.requiresBookerEmailVerification
|
||||
);
|
||||
expect(updatedEventType.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(updatedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
body.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(updatedEventType.color).toEqual(body.color);
|
||||
|
||||
eventType.title = newTitle;
|
||||
eventType.scheduleId = secondSchedule.id;
|
||||
@@ -332,7 +718,14 @@ describe("Event types Endpoints", () => {
|
||||
eventType.bookingLimitsDuration = updatedEventType.bookingLimitsDuration;
|
||||
eventType.offsetStart = updatedEventType.offsetStart;
|
||||
eventType.bookingWindow = updatedEventType.bookingWindow;
|
||||
eventType.bookerLayouts = updatedEventType.bookerLayouts;
|
||||
eventType.confirmationPolicy = updatedEventType.confirmationPolicy;
|
||||
eventType.recurrence = updatedEventType.recurrence;
|
||||
eventType.customName = updatedEventType.customName;
|
||||
eventType.requiresBookerEmailVerification = updatedEventType.requiresBookerEmailVerification;
|
||||
eventType.hideCalendarNotes = updatedEventType.hideCalendarNotes;
|
||||
eventType.lockTimeZoneToggleOnBookingPage = updatedEventType.lockTimeZoneToggleOnBookingPage;
|
||||
eventType.color = updatedEventType.color;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -373,38 +766,18 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType.recurrence).toEqual(eventType.recurrence);
|
||||
});
|
||||
|
||||
it(`/GET/even-types by username`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.[0];
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType?.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
expect(fetchedEventType.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType.requiresBookerEmailVerification).toEqual(
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType.color).toEqual(eventType.color);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and eventSlug`, async () => {
|
||||
@@ -430,7 +803,18 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType.requiresBookerEmailVerification).toEqual(
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType.color).toEqual(eventType.color);
|
||||
});
|
||||
|
||||
it(`/GET/:id not existing`, async () => {
|
||||
|
||||
+27
-9
@@ -3,7 +3,9 @@ import { DeleteEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2
|
||||
import { GetEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-type.output";
|
||||
import { GetEventTypesOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-types.output";
|
||||
import { UpdateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/update-event-type.output";
|
||||
import { EventTypeResponseTransformPipe } from "@/ee/event-types/event-types_2024_06_14/pipes/event-type-response.transformer";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { VERSION_2024_06_14_VALUE } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
@@ -23,14 +25,15 @@ import {
|
||||
HttpStatus,
|
||||
Delete,
|
||||
Query,
|
||||
ParseIntPipe,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
|
||||
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
@@ -45,7 +48,11 @@ import {
|
||||
required: true,
|
||||
})
|
||||
export class EventTypesController_2024_06_14 {
|
||||
constructor(private readonly eventTypesService: EventTypesService_2024_06_14) {}
|
||||
constructor(
|
||||
private readonly eventTypesService: EventTypesService_2024_06_14,
|
||||
private readonly inputEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly eventTypeResponseTransformPipe: EventTypeResponseTransformPipe
|
||||
) {}
|
||||
|
||||
@Post("/")
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@@ -60,11 +67,16 @@ export class EventTypesController_2024_06_14 {
|
||||
@Body() body: CreateEventTypeInput_2024_06_14,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<CreateEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.createUserEventType(user, body);
|
||||
const transformedBody = await this.inputEventTypesService.transformAndValidateCreateEventTypeInput(
|
||||
user.id,
|
||||
body
|
||||
);
|
||||
|
||||
const eventType = await this.eventTypesService.createUserEventType(user, transformedBody);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: this.eventTypeResponseTransformPipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,7 +101,7 @@ export class EventTypesController_2024_06_14 {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: this.eventTypeResponseTransformPipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +113,7 @@ export class EventTypesController_2024_06_14 {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
data: this.eventTypeResponseTransformPipe.transform(eventTypes),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,15 +128,21 @@ export class EventTypesController_2024_06_14 {
|
||||
})
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async updateEventType(
|
||||
@Param("eventTypeId") eventTypeId: number,
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||
@Body() body: UpdateEventTypeInput_2024_06_14,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<UpdateEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
|
||||
const transformedBody = await this.inputEventTypesService.transformAndValidateUpdateEventTypeInput(
|
||||
body,
|
||||
user.id,
|
||||
eventTypeId
|
||||
);
|
||||
|
||||
const eventType = await this.eventTypesService.updateEventType(eventTypeId, transformedBody, user);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: this.eventTypeResponseTransformPipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesController_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/controllers/event-types.controller";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { EventTypeResponseTransformPipe } from "@/ee/event-types/event-types_2024_06_14/pipes/event-type-response.transformer";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
|
||||
@@ -22,6 +27,11 @@ import { Module } from "@nestjs/common";
|
||||
UsersRepository,
|
||||
UsersService,
|
||||
SchedulesRepository_2024_06_11,
|
||||
EventTypeResponseTransformPipe,
|
||||
CalendarsService,
|
||||
CredentialsRepository,
|
||||
AppsRepository,
|
||||
CalendarsRepository,
|
||||
],
|
||||
controllers: [EventTypesController_2024_06_14],
|
||||
exports: [
|
||||
|
||||
@@ -4,36 +4,10 @@ import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
getEventTypeById,
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformLocationsApiToInternal,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
import { getEventTypeById } from "@calcom/platform-libraries";
|
||||
import { InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
type InputEventTransformed = Omit<
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
| "lengthInMinutes"
|
||||
| "locations"
|
||||
| "bookingFields"
|
||||
| "bookingLimitsCount"
|
||||
| "onlyShowFirstAvailableSlot"
|
||||
| "bookingLimitsDuration"
|
||||
| "offsetStart"
|
||||
| "periodType"
|
||||
| "periodDays"
|
||||
| "periodCountCalendarDays"
|
||||
| "periodStartDate"
|
||||
| "periodEndDate"
|
||||
| "recurrence"
|
||||
> & {
|
||||
length: number;
|
||||
slug: string;
|
||||
locations?: ReturnType<typeof transformLocationsApiToInternal>;
|
||||
bookingFields?: ReturnType<typeof transformBookingFieldsApiToInternal>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository_2024_06_14 {
|
||||
constructor(
|
||||
@@ -42,7 +16,10 @@ export class EventTypesRepository_2024_06_14 {
|
||||
private usersService: UsersService
|
||||
) {}
|
||||
|
||||
async createUserEventType(userId: number, body: InputEventTransformed) {
|
||||
async createUserEventType(
|
||||
userId: number,
|
||||
body: Omit<InputEventTransformed_2024_06_14, "destinationCalendar">
|
||||
) {
|
||||
return this.dbWrite.prisma.eventType.create({
|
||||
data: {
|
||||
...body,
|
||||
@@ -57,7 +34,19 @@ export class EventTypesRepository_2024_06_14 {
|
||||
async getEventTypeWithSeats(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
select: { users: { select: { id: true } }, seatsPerTimeSlot: true },
|
||||
select: {
|
||||
users: { select: { id: true } },
|
||||
seatsPerTimeSlot: true,
|
||||
locations: true,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeWithMetaData(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
select: { metadata: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,7 +56,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,7 +65,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,7 +87,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
async getEventTypeById(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: { users: true, schedule: true },
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,7 +106,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
slug: slug,
|
||||
},
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { Injectable, PipeTransform } from "@nestjs/common";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
import {
|
||||
DatabaseEventType,
|
||||
OutputEventTypesService_2024_06_14,
|
||||
} from "../services/output-event-types.service";
|
||||
|
||||
type EventTypeResponse = DatabaseEventType & { ownerId: number };
|
||||
|
||||
@Injectable()
|
||||
export class EventTypeResponseTransformPipe implements PipeTransform {
|
||||
constructor(private readonly outputEventTypesService: OutputEventTypesService_2024_06_14) {}
|
||||
|
||||
private transformEventType(eventType: EventTypeResponse): EventTypeOutput_2024_06_14 {
|
||||
return plainToClass(
|
||||
EventTypeOutput_2024_06_14,
|
||||
this.outputEventTypesService.getResponseEventType(eventType.ownerId, eventType),
|
||||
{ strategy: "exposeAll" }
|
||||
);
|
||||
}
|
||||
|
||||
// Implementing function overloading to ensure correct return types based on input type:
|
||||
transform(value: EventTypeResponse[]): EventTypeOutput_2024_06_14[];
|
||||
|
||||
transform(value: EventTypeResponse): EventTypeOutput_2024_06_14;
|
||||
|
||||
transform(
|
||||
value: EventTypeResponse | EventTypeResponse[]
|
||||
): EventTypeOutput_2024_06_14 | EventTypeOutput_2024_06_14[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.transformEventType(item));
|
||||
} else {
|
||||
return this.transformEventType(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-49
@@ -1,7 +1,5 @@
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_06_14/constants/constants";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
@@ -13,20 +11,13 @@ import { BadRequestException, ForbiddenException, Injectable, NotFoundException
|
||||
import { createEventType, updateEventType } from "@calcom/platform-libraries";
|
||||
import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-libraries";
|
||||
import { dynamicEvent } from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { GetEventTypesQuery_2024_06_14, InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesService_2024_06_14 {
|
||||
constructor(
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly inputEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14,
|
||||
private readonly membershipsRepository: MembershipsRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly usersService: UsersService,
|
||||
@@ -35,22 +26,13 @@ export class EventTypesService_2024_06_14 {
|
||||
private readonly schedulesRepository: SchedulesRepository_2024_06_11
|
||||
) {}
|
||||
|
||||
async createUserEventType(user: UserWithProfile, body: CreateEventTypeInput_2024_06_14) {
|
||||
async createUserEventType(user: UserWithProfile, body: InputEventTransformed_2024_06_14) {
|
||||
await this.checkCanCreateEventType(user.id, body);
|
||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||
const {
|
||||
bookingLimits,
|
||||
durationLimits,
|
||||
periodType = undefined,
|
||||
periodDays = undefined,
|
||||
periodCountCalendarDays = undefined,
|
||||
periodStartDate = undefined,
|
||||
periodEndDate = undefined,
|
||||
recurrence = undefined,
|
||||
...bodyTransformed
|
||||
} = this.inputEventTypesService.transformInputCreateEventType(body);
|
||||
const { destinationCalendar: _destinationCalendar, ...rest } = body;
|
||||
|
||||
const { eventType: eventTypeCreated } = await createEventType({
|
||||
input: bodyTransformed,
|
||||
input: rest,
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -62,15 +44,7 @@ export class EventTypesService_2024_06_14 {
|
||||
await updateEventType({
|
||||
input: {
|
||||
id: eventTypeCreated.id,
|
||||
bookingLimits,
|
||||
durationLimits,
|
||||
periodType,
|
||||
periodDays,
|
||||
periodCountCalendarDays,
|
||||
periodStartDate,
|
||||
periodEndDate,
|
||||
recurrence,
|
||||
...bodyTransformed,
|
||||
...body,
|
||||
},
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
@@ -86,10 +60,13 @@ export class EventTypesService_2024_06_14 {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeCreated.id} not found`);
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
return {
|
||||
ownerId: user.id,
|
||||
...eventType,
|
||||
};
|
||||
}
|
||||
|
||||
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput_2024_06_14) {
|
||||
async checkCanCreateEventType(userId: number, body: InputEventTransformed_2024_06_14) {
|
||||
const existsWithSlug = await this.eventTypesRepository.getUserEventTypeBySlug(userId, body.slug);
|
||||
if (existsWithSlug) {
|
||||
throw new BadRequestException("User already has an event type with this slug.");
|
||||
@@ -109,7 +86,10 @@ export class EventTypesService_2024_06_14 {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
return {
|
||||
ownerId: user.id,
|
||||
...eventType,
|
||||
};
|
||||
}
|
||||
|
||||
async getEventTypesByUsername(username: string) {
|
||||
@@ -117,7 +97,7 @@ export class EventTypesService_2024_06_14 {
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
return this.getUserEventTypes(user.id);
|
||||
return await this.getUserEventTypes(user.id);
|
||||
}
|
||||
|
||||
async getUserToCreateEvent(user: UserWithProfile) {
|
||||
@@ -147,17 +127,19 @@ export class EventTypesService_2024_06_14 {
|
||||
}
|
||||
|
||||
this.checkUserOwnsEventType(userId, eventType);
|
||||
return this.outputEventTypesService.getResponseEventType(userId, eventType);
|
||||
|
||||
return {
|
||||
ownerId: userId,
|
||||
...eventType,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserEventTypes(userId: number) {
|
||||
const eventTypes = await this.eventTypesRepository.getUserEventTypes(userId);
|
||||
|
||||
const eventTypePromises = eventTypes.map(async (eventType) => {
|
||||
return await this.outputEventTypesService.getResponseEventType(userId, eventType);
|
||||
return eventTypes.map((eventType) => {
|
||||
return { ownerId: userId, ...eventType };
|
||||
});
|
||||
|
||||
return await Promise.all(eventTypePromises);
|
||||
}
|
||||
|
||||
async getUserEventTypeForAtom(user: UserWithProfile, eventTypeId: number) {
|
||||
@@ -190,7 +172,7 @@ export class EventTypesService_2024_06_14 {
|
||||
return await getEventTypesPublic(user.id);
|
||||
}
|
||||
|
||||
async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14): Promise<EventTypeOutput_2024_06_14[]> {
|
||||
async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14) {
|
||||
const { username, eventSlug, usernames } = queryParams;
|
||||
|
||||
if (username && eventSlug) {
|
||||
@@ -218,12 +200,12 @@ export class EventTypesService_2024_06_14 {
|
||||
usersFiltered.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(0, {
|
||||
return {
|
||||
ownerId: 0,
|
||||
...dynamicEvent,
|
||||
users: usersFiltered,
|
||||
isInstantEvent: false,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async createUserDefaultEventTypes(userId: number) {
|
||||
@@ -239,12 +221,12 @@ export class EventTypesService_2024_06_14 {
|
||||
return defaultEventTypes;
|
||||
}
|
||||
|
||||
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_06_14, user: UserWithProfile) {
|
||||
async updateEventType(eventTypeId: number, body: InputEventTransformed_2024_06_14, user: UserWithProfile) {
|
||||
await this.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
|
||||
const eventTypeUser = await this.getUserToUpdateEvent(user);
|
||||
const bodyTransformed = this.inputEventTypesService.transformInputUpdateEventType(body);
|
||||
|
||||
await updateEventType({
|
||||
input: { id: eventTypeId, ...bodyTransformed },
|
||||
input: { id: eventTypeId, ...body },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -259,7 +241,10 @@ export class EventTypesService_2024_06_14 {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
return {
|
||||
ownerId: user.id,
|
||||
...eventType,
|
||||
};
|
||||
}
|
||||
|
||||
async checkCanUpdateEventType(userId: number, eventTypeId: number, scheduleId: number | undefined) {
|
||||
|
||||
+259
-6
@@ -1,4 +1,8 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConnectedCalendarsData } from "@/ee/calendars/outputs/connected-calendars.output";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, BadRequestException } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
transformBookingFieldsApiToInternal,
|
||||
@@ -9,12 +13,85 @@ import {
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemAfterFieldRescheduleReason,
|
||||
EventTypeMetaDataSchema,
|
||||
systemBeforeFieldLocation,
|
||||
transformBookerLayoutsApiToInternal,
|
||||
transformConfirmationPolicyApiToInternal,
|
||||
transformEventColorsApiToInternal,
|
||||
validateCustomEventName,
|
||||
transformSeatsApiToInternal,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { systemBeforeFieldLocation } from "@calcom/platform-libraries";
|
||||
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
DestinationCalendar_2024_06_14,
|
||||
InputEventTransformed_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import { OutputEventTypesService_2024_06_14 } from "./output-event-types.service";
|
||||
|
||||
interface ValidationContext {
|
||||
eventTypeId?: number;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
locations?: InputEventTransformed_2024_06_14["locations"];
|
||||
requiresConfirmation?: boolean;
|
||||
eventName?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class InputEventTypesService_2024_06_14 {
|
||||
constructor(
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14,
|
||||
private readonly calendarsService: CalendarsService
|
||||
) {}
|
||||
|
||||
async transformAndValidateCreateEventTypeInput(
|
||||
userId: UserWithProfile["id"],
|
||||
inputEventType: CreateEventTypeInput_2024_06_14
|
||||
) {
|
||||
const transformedBody = this.transformInputCreateEventType(inputEventType);
|
||||
|
||||
await this.validateEventTypeInputs({
|
||||
seatsPerTimeSlot: transformedBody.seatsPerTimeSlot,
|
||||
locations: transformedBody.locations,
|
||||
requiresConfirmation: transformedBody.requiresConfirmation,
|
||||
eventName: transformedBody.eventName,
|
||||
});
|
||||
|
||||
transformedBody.destinationCalendar &&
|
||||
(await this.validateInputDestinationCalendar(userId, transformedBody.destinationCalendar));
|
||||
|
||||
transformedBody.useEventTypeDestinationCalendarEmail &&
|
||||
(await this.validateInputUseDestinationCalendarEmail(userId));
|
||||
|
||||
return transformedBody;
|
||||
}
|
||||
|
||||
async transformAndValidateUpdateEventTypeInput(
|
||||
inputEventType: UpdateEventTypeInput_2024_06_14,
|
||||
userId: UserWithProfile["id"],
|
||||
eventTypeId: number
|
||||
) {
|
||||
const transformedBody = await this.transformInputUpdateEventType(inputEventType, eventTypeId);
|
||||
|
||||
await this.validateEventTypeInputs({
|
||||
eventTypeId: eventTypeId,
|
||||
seatsPerTimeSlot: transformedBody.seatsPerTimeSlot,
|
||||
locations: transformedBody.locations,
|
||||
requiresConfirmation: transformedBody.requiresConfirmation,
|
||||
eventName: transformedBody.eventName,
|
||||
});
|
||||
|
||||
transformedBody.destinationCalendar &&
|
||||
(await this.validateInputDestinationCalendar(userId, transformedBody.destinationCalendar));
|
||||
|
||||
transformedBody.useEventTypeDestinationCalendarEmail &&
|
||||
(await this.validateInputUseDestinationCalendarEmail(userId));
|
||||
|
||||
return transformedBody;
|
||||
}
|
||||
|
||||
transformInputCreateEventType(inputEventType: CreateEventTypeInput_2024_06_14) {
|
||||
const defaultLocations: CreateEventTypeInput_2024_06_14["locations"] = [
|
||||
{
|
||||
@@ -30,9 +107,16 @@ export class InputEventTypesService_2024_06_14 {
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookerLayouts,
|
||||
confirmationPolicy,
|
||||
color,
|
||||
recurrence,
|
||||
seats,
|
||||
customName,
|
||||
useDestinationCalendarEmail,
|
||||
...rest
|
||||
} = inputEventType;
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
|
||||
const hasMultipleLocations = (locations || defaultLocations).length > 1;
|
||||
const eventType = {
|
||||
@@ -45,25 +129,47 @@ export class InputEventTypesService_2024_06_14 {
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
: undefined,
|
||||
...this.transformInputBookingWindow(bookingWindow),
|
||||
metadata: {
|
||||
bookerLayouts: this.transformInputBookerLayouts(bookerLayouts),
|
||||
requiresConfirmationThreshold:
|
||||
confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined,
|
||||
},
|
||||
requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined,
|
||||
requiresConfirmationWillBlockSlot:
|
||||
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
|
||||
eventTypeColor: this.transformInputEventTypeColor(color),
|
||||
recurringEvent: recurrence ? this.transformInputRecurrignEvent(recurrence) : undefined,
|
||||
...this.transformInputSeatOptions(seats),
|
||||
eventName: customName,
|
||||
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
|
||||
};
|
||||
|
||||
return eventType;
|
||||
}
|
||||
|
||||
transformInputUpdateEventType(inputEventType: UpdateEventTypeInput_2024_06_14) {
|
||||
async transformInputUpdateEventType(inputEventType: UpdateEventTypeInput_2024_06_14, eventTypeId: number) {
|
||||
const {
|
||||
lengthInMinutes,
|
||||
locations,
|
||||
bookingFields,
|
||||
scheduleId,
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookerLayouts,
|
||||
confirmationPolicy,
|
||||
color,
|
||||
recurrence,
|
||||
seats,
|
||||
customName,
|
||||
useDestinationCalendarEmail,
|
||||
...rest
|
||||
} = inputEventType;
|
||||
const eventTypeDb = await this.eventTypesRepository.getEventTypeWithMetaData(eventTypeId);
|
||||
const metadataTransformed = !!eventTypeDb?.metadata
|
||||
? EventTypeMetaDataSchema.parse(eventTypeDb.metadata)
|
||||
: {};
|
||||
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
const hasMultipleLocations = !!(locations && locations?.length > 1);
|
||||
|
||||
const eventType = {
|
||||
@@ -73,13 +179,25 @@ export class InputEventTypesService_2024_06_14 {
|
||||
bookingFields: bookingFields
|
||||
? this.transformInputBookingFields(bookingFields, hasMultipleLocations)
|
||||
: undefined,
|
||||
schedule: scheduleId,
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
: undefined,
|
||||
...this.transformInputBookingWindow(bookingWindow),
|
||||
metadata: {
|
||||
...metadataTransformed,
|
||||
bookerLayouts: this.transformInputBookerLayouts(bookerLayouts),
|
||||
requiresConfirmationThreshold:
|
||||
confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined,
|
||||
},
|
||||
recurringEvent: recurrence ? this.transformInputRecurrignEvent(recurrence) : undefined,
|
||||
requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined,
|
||||
requiresConfirmationWillBlockSlot:
|
||||
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
|
||||
eventTypeColor: this.transformInputEventTypeColor(color),
|
||||
...this.transformInputSeatOptions(seats),
|
||||
eventName: customName,
|
||||
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
|
||||
};
|
||||
|
||||
return eventType;
|
||||
@@ -114,7 +232,142 @@ export class InputEventTypesService_2024_06_14 {
|
||||
return !!res ? res : {};
|
||||
}
|
||||
|
||||
transformInputBookerLayouts(inputBookerLayouts: CreateEventTypeInput_2024_06_14["bookerLayouts"]) {
|
||||
return transformBookerLayoutsApiToInternal(inputBookerLayouts);
|
||||
}
|
||||
|
||||
transformInputConfirmationPolicy(
|
||||
requiresConfirmation: CreateEventTypeInput_2024_06_14["confirmationPolicy"]
|
||||
) {
|
||||
return transformConfirmationPolicyApiToInternal(requiresConfirmation);
|
||||
}
|
||||
transformInputRecurrignEvent(recurrence: CreateEventTypeInput_2024_06_14["recurrence"]) {
|
||||
return transformRecurrenceApiToInternal(recurrence);
|
||||
}
|
||||
|
||||
transformInputEventTypeColor(color: CreateEventTypeInput_2024_06_14["color"]) {
|
||||
return transformEventColorsApiToInternal(color);
|
||||
}
|
||||
|
||||
transformInputSeatOptions(seats: CreateEventTypeInput_2024_06_14["seats"]) {
|
||||
return transformSeatsApiToInternal(seats);
|
||||
}
|
||||
async validateEventTypeInputs({
|
||||
eventTypeId,
|
||||
seatsPerTimeSlot,
|
||||
locations,
|
||||
requiresConfirmation,
|
||||
eventName,
|
||||
}: ValidationContext) {
|
||||
let seatsPerTimeSlotDb: number | null = null;
|
||||
let locationsDb: ReturnType<typeof this.transformInputLocations> = [];
|
||||
let requiresConfirmationDb = false;
|
||||
|
||||
if (eventTypeId != null) {
|
||||
const eventTypeDb = await this.eventTypesRepository.getEventTypeWithSeats(eventTypeId);
|
||||
seatsPerTimeSlotDb = eventTypeDb?.seatsPerTimeSlot ?? null;
|
||||
locationsDb = this.outputEventTypesService.transformLocations(eventTypeDb?.locations) ?? [];
|
||||
requiresConfirmationDb = eventTypeDb?.requiresConfirmation ?? false;
|
||||
}
|
||||
|
||||
const seatsPerTimeSlotFinal = seatsPerTimeSlot !== undefined ? seatsPerTimeSlot : seatsPerTimeSlotDb;
|
||||
const seatsEnabledFinal = seatsPerTimeSlotFinal != null && seatsPerTimeSlotFinal > 0;
|
||||
|
||||
const locationsFinal = locations !== undefined ? locations : locationsDb;
|
||||
const requiresConfirmationFinal =
|
||||
requiresConfirmation !== undefined ? requiresConfirmation : requiresConfirmationDb;
|
||||
|
||||
this.validateSeatsSingleLocationRule(seatsEnabledFinal, locationsFinal);
|
||||
this.validateSeatsRequiresConfirmationFalseRule(seatsEnabledFinal, requiresConfirmationFinal);
|
||||
this.validateMultipleLocationsSeatsDisabledRule(locationsFinal, seatsEnabledFinal);
|
||||
this.validateRequiresConfirmationSeatsDisabledRule(requiresConfirmationFinal, seatsEnabledFinal);
|
||||
|
||||
if (eventName) {
|
||||
await this.validateCustomEventNameInput(eventName);
|
||||
}
|
||||
}
|
||||
validateSeatsSingleLocationRule(
|
||||
seatsEnabled: boolean,
|
||||
locations: ReturnType<typeof this.transformInputLocations>
|
||||
) {
|
||||
if (seatsEnabled && locations.length > 1) {
|
||||
throw new BadRequestException(
|
||||
"Seats Validation failed: Seats are enabled but more than one location provided."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
validateSeatsRequiresConfirmationFalseRule(seatsEnabled: boolean, requiresConfirmation: boolean) {
|
||||
if (seatsEnabled && requiresConfirmation) {
|
||||
throw new BadRequestException(
|
||||
"Seats Validation failed: Seats are enabled but requiresConfirmation is true."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
validateMultipleLocationsSeatsDisabledRule(
|
||||
locations: ReturnType<typeof this.transformInputLocations>,
|
||||
seatsEnabled: boolean
|
||||
) {
|
||||
if (locations.length > 1 && seatsEnabled) {
|
||||
throw new BadRequestException("Locations Validation failed: Multiple locations but seats are enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
validateRequiresConfirmationSeatsDisabledRule(requiresConfirmation: boolean, seatsEnabled: boolean) {
|
||||
if (requiresConfirmation && seatsEnabled) {
|
||||
throw new BadRequestException(
|
||||
"RequiresConfirmation Validation failed: Seats are enabled but requiresConfirmation is true."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async validateCustomEventNameInput(value: string) {
|
||||
const validationResult = validateCustomEventName(value);
|
||||
if (validationResult !== true) {
|
||||
throw new BadRequestException(`Invalid event name variables: ${validationResult}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async validateInputDestinationCalendar(
|
||||
userId: number,
|
||||
destinationCalendar: DestinationCalendar_2024_06_14
|
||||
) {
|
||||
const calendars: ConnectedCalendarsData = await this.calendarsService.getCalendars(userId);
|
||||
|
||||
const allCals = calendars.connectedCalendars.map((cal) => cal.calendars ?? []).flat();
|
||||
|
||||
const matchedCalendar = allCals.find(
|
||||
(cal) =>
|
||||
cal.externalId === destinationCalendar.externalId &&
|
||||
cal.integration === destinationCalendar.integration
|
||||
);
|
||||
|
||||
if (!matchedCalendar) {
|
||||
throw new BadRequestException("Invalid destinationCalendarId: Calendar does not exist");
|
||||
}
|
||||
|
||||
if (matchedCalendar.readOnly) {
|
||||
throw new BadRequestException("Invalid destinationCalendarId: Calendar does not have write permission");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async validateInputUseDestinationCalendarEmail(userId: number) {
|
||||
const calendars: ConnectedCalendarsData = await this.calendarsService.getCalendars(userId);
|
||||
|
||||
const allCals = calendars.connectedCalendars.map((cal) => cal.calendars ?? []).flat();
|
||||
|
||||
const primaryCalendar = allCals.find((cal) => cal.primary);
|
||||
|
||||
if (!primaryCalendar) {
|
||||
throw new BadRequestException(
|
||||
"Validation failed: A primary connected calendar is required to set useDestinationCalendarEmail"
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+98
-12
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import type { EventType, User, Schedule } from "@prisma/client";
|
||||
import type { EventType, User, Schedule, DestinationCalendar } from "@prisma/client";
|
||||
|
||||
import {
|
||||
EventTypeMetaDataSchema,
|
||||
@@ -15,11 +15,25 @@ import {
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
transformBookerLayoutsInternalToApi,
|
||||
transformRequiresConfirmationInternalToApi,
|
||||
transformEventTypeColorsInternalToApi,
|
||||
parseEventTypeColor,
|
||||
transformSeatsInternalToApi,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { TransformFutureBookingsLimitSchema_2024_06_14 } from "@calcom/platform-types";
|
||||
import {
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
BookerLayoutsTransformedSchema,
|
||||
NoticeThresholdTransformedSchema,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
type EventTypeRelations = { users: User[]; schedule: Schedule | null };
|
||||
type DatabaseEventType = EventType & EventTypeRelations;
|
||||
type EventTypeRelations = {
|
||||
users: User[];
|
||||
schedule: Schedule | null;
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
};
|
||||
export type DatabaseEventType = EventType & EventTypeRelations;
|
||||
|
||||
type Input = Pick<
|
||||
DatabaseEventType,
|
||||
@@ -58,11 +72,19 @@ type Input = Pick<
|
||||
| "periodCountCalendarDays"
|
||||
| "periodStartDate"
|
||||
| "periodEndDate"
|
||||
| "requiresBookerEmailVerification"
|
||||
| "hideCalendarNotes"
|
||||
| "eventTypeColor"
|
||||
| "seatsShowAttendees"
|
||||
| "requiresConfirmationWillBlockSlot"
|
||||
| "eventName"
|
||||
| "destinationCalendar"
|
||||
| "useEventTypeDestinationCalendarEmail"
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class OutputEventTypesService_2024_06_14 {
|
||||
async getResponseEventType(ownerId: number, databaseEventType: Input) {
|
||||
getResponseEventType(ownerId: number, databaseEventType: Input): EventTypeOutput_2024_06_14 {
|
||||
const {
|
||||
id,
|
||||
length,
|
||||
@@ -74,8 +96,6 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
slug,
|
||||
schedulingType,
|
||||
requiresConfirmation,
|
||||
price,
|
||||
currency,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
@@ -87,17 +107,34 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
scheduleId,
|
||||
onlyShowFirstAvailableSlot,
|
||||
offsetStart,
|
||||
requiresBookerEmailVerification,
|
||||
hideCalendarNotes,
|
||||
seatsShowAttendees,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
} = databaseEventType;
|
||||
|
||||
const locations = this.transformLocations(databaseEventType.locations);
|
||||
const customName = databaseEventType?.eventName ?? undefined;
|
||||
const bookingFields = databaseEventType.bookingFields
|
||||
? this.transformBookingFields(BookingFieldsSchema.parse(databaseEventType.bookingFields))
|
||||
: [];
|
||||
const recurrence = this.transformRecurringEvent(databaseEventType.recurringEvent);
|
||||
const metadata = this.transformMetadata(databaseEventType.metadata) || {};
|
||||
const users = this.transformUsers(databaseEventType.users);
|
||||
const users = this.transformUsers(databaseEventType.users || []);
|
||||
const bookingLimitsCount = this.transformIntervalLimits(databaseEventType.bookingLimits);
|
||||
const bookingLimitsDuration = this.transformIntervalLimits(databaseEventType.durationLimits);
|
||||
const color = this.transformEventTypeColor(databaseEventType.eventTypeColor);
|
||||
const bookerLayouts = this.transformBookerLayouts(
|
||||
metadata.bookerLayouts as unknown as BookerLayoutsTransformedSchema
|
||||
);
|
||||
const confirmationPolicy = this.transformRequiresConfirmation(
|
||||
!!databaseEventType.requiresConfirmation,
|
||||
!!databaseEventType.requiresConfirmationWillBlockSlot,
|
||||
metadata.requiresConfirmationThreshold as NoticeThresholdTransformedSchema
|
||||
);
|
||||
delete metadata["bookerLayouts"];
|
||||
delete metadata["requiresConfirmationThreshold"];
|
||||
const seats = this.transformSeats(seatsPerTimeSlot, seatsShowAttendees, seatsShowAvailabilityCount);
|
||||
const bookingWindow = this.transformBookingWindow({
|
||||
periodType: databaseEventType.periodType,
|
||||
periodDays: databaseEventType.periodDays,
|
||||
@@ -105,6 +142,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
periodStartDate: databaseEventType.periodStartDate,
|
||||
periodEndDate: databaseEventType.periodEndDate,
|
||||
} as TransformFutureBookingsLimitSchema_2024_06_14);
|
||||
const destinationCalendar = this.transformDestinationCalendar(databaseEventType.destinationCalendar);
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -121,16 +159,12 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
minimumBookingNotice,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
schedulingType,
|
||||
metadata,
|
||||
requiresConfirmation,
|
||||
price,
|
||||
currency,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
seatsPerTimeSlot,
|
||||
forwardParamsSuccessRedirect,
|
||||
successRedirectUrl,
|
||||
seatsShowAvailabilityCount,
|
||||
isInstantEvent,
|
||||
users,
|
||||
scheduleId,
|
||||
@@ -139,6 +173,15 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
onlyShowFirstAvailableSlot,
|
||||
offsetStart,
|
||||
bookingWindow,
|
||||
bookerLayouts,
|
||||
confirmationPolicy,
|
||||
requiresBookerEmailVerification,
|
||||
hideCalendarNotes,
|
||||
color,
|
||||
seats,
|
||||
customName,
|
||||
destinationCalendar,
|
||||
useDestinationCalendarEmail: useEventTypeDestinationCalendarEmail,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,6 +190,14 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
return transformLocationsInternalToApi(TransformedLocationsSchema.parse(locations));
|
||||
}
|
||||
|
||||
transformDestinationCalendar(destinationCalendar?: DestinationCalendar | null) {
|
||||
if (!destinationCalendar) return undefined;
|
||||
return {
|
||||
integration: destinationCalendar.integration,
|
||||
externalId: destinationCalendar.externalId,
|
||||
};
|
||||
}
|
||||
|
||||
transformBookingFields(bookingFields: (SystemField | CustomField)[] | null) {
|
||||
if (!bookingFields) return [];
|
||||
|
||||
@@ -189,4 +240,39 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
transformBookingWindow(bookingLimits: TransformFutureBookingsLimitSchema_2024_06_14) {
|
||||
return transformFutureBookingLimitsInternalToApi(bookingLimits);
|
||||
}
|
||||
|
||||
transformBookerLayouts(bookerLayouts: BookerLayoutsTransformedSchema) {
|
||||
if (!bookerLayouts) return undefined;
|
||||
return transformBookerLayoutsInternalToApi(bookerLayouts);
|
||||
}
|
||||
|
||||
transformRequiresConfirmation(
|
||||
requiresConfirmation: boolean,
|
||||
requiresConfirmationWillBlockSlot: boolean,
|
||||
requiresConfirmationThreshold?: NoticeThresholdTransformedSchema
|
||||
) {
|
||||
return transformRequiresConfirmationInternalToApi(
|
||||
requiresConfirmation,
|
||||
requiresConfirmationWillBlockSlot,
|
||||
requiresConfirmationThreshold
|
||||
);
|
||||
}
|
||||
|
||||
transformEventTypeColor(eventTypeColor: any) {
|
||||
if (!eventTypeColor) return undefined;
|
||||
const parsedeventTypeColor = parseEventTypeColor(eventTypeColor);
|
||||
return transformEventTypeColorsInternalToApi(parsedeventTypeColor);
|
||||
}
|
||||
|
||||
transformSeats(
|
||||
seatsPerTimeSlot: number | null,
|
||||
seatsShowAttendees: boolean | null,
|
||||
seatsShowAvailabilityCount: boolean | null
|
||||
) {
|
||||
return transformSeatsInternalToApi({
|
||||
seatsPerTimeSlot,
|
||||
seatsShowAttendees: !!seatsShowAttendees,
|
||||
seatsShowAvailabilityCount: !!seatsShowAvailabilityCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+42
-11
@@ -1,5 +1,6 @@
|
||||
import { CreatePhoneCallInput } from "@/ee/event-types/event-types_2024_06_14/inputs/create-phone-call.input";
|
||||
import { CreatePhoneCallOutput } from "@/ee/event-types/event-types_2024_06_14/outputs/create-phone-call.output";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
@@ -15,7 +16,10 @@ import { DeleteTeamEventTypeOutput } from "@/modules/organizations/controllers/e
|
||||
import { GetTeamEventTypeOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/get-team-event-type.output";
|
||||
import { GetTeamEventTypesOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/get-team-event-types.output";
|
||||
import { UpdateTeamEventTypeOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/update-team-event-type.output";
|
||||
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/controllers/pipes/event-types/team-event-types-response.transformer";
|
||||
import { InputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/input.service";
|
||||
import { OrganizationsEventTypesService } from "@/modules/organizations/services/event-types/organizations-event-types.service";
|
||||
import { DatabaseTeamEventType } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import {
|
||||
Controller,
|
||||
@@ -34,22 +38,33 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { handleCreatePhoneCall } from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
GetTeamEventTypesQuery_2024_06_14,
|
||||
SkipTakePagination,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
UpdateTeamEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export type EventTypeHandlerResponse = {
|
||||
data: DatabaseTeamEventType[] | DatabaseTeamEventType;
|
||||
status: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
};
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@DocsTags("Organizations Event Types")
|
||||
export class OrganizationsEventTypesController {
|
||||
constructor(private readonly organizationsEventTypesService: OrganizationsEventTypesService) {}
|
||||
constructor(
|
||||
private readonly organizationsEventTypesService: OrganizationsEventTypesService,
|
||||
private readonly inputService: InputOrganizationsEventTypesService,
|
||||
private readonly inputUserEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly outputTeamEventTypesResponsePipe: OutputTeamEventTypesResponsePipe
|
||||
) {}
|
||||
|
||||
@Roles("TEAM_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@@ -61,16 +76,22 @@ export class OrganizationsEventTypesController {
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Body() bodyEventType: CreateTeamEventTypeInput_2024_06_14
|
||||
): Promise<CreateTeamEventTypeOutput> {
|
||||
const transformedBody = await this.inputService.transformAndValidateCreateTeamEventTypeInput(
|
||||
user.id,
|
||||
teamId,
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.createTeamEventType(
|
||||
user,
|
||||
teamId,
|
||||
orgId,
|
||||
bodyEventType
|
||||
transformedBody
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,7 +111,9 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: (await this.outputTeamEventTypesResponsePipe.transform(
|
||||
eventType
|
||||
)) as TeamEventTypeOutput_2024_06_14,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,12 +148,13 @@ export class OrganizationsEventTypesController {
|
||||
@Query() queryParams: GetTeamEventTypesQuery_2024_06_14
|
||||
): Promise<GetTeamEventTypesOutput> {
|
||||
const { eventSlug } = queryParams;
|
||||
|
||||
if (eventSlug) {
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(teamId, eventSlug);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType ? [eventType] : [],
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType ? [eventType] : []),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,7 +162,7 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventTypes),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,7 +179,7 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventTypes),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,16 +193,23 @@ export class OrganizationsEventTypesController {
|
||||
@GetUser() user: UserWithProfile,
|
||||
@Body() bodyEventType: UpdateTeamEventTypeInput_2024_06_14
|
||||
): Promise<UpdateTeamEventTypeOutput> {
|
||||
const transformedBody = await this.inputService.transformAndValidateUpdateTeamEventTypeInput(
|
||||
user.id,
|
||||
eventTypeId,
|
||||
teamId,
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.updateTeamEventType(
|
||||
eventTypeId,
|
||||
teamId,
|
||||
bodyEventType,
|
||||
transformedBody,
|
||||
user
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -189,7 +220,7 @@ export class OrganizationsEventTypesController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async deleteTeamEventType(
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Param("eventTypeId") eventTypeId: number
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
|
||||
): Promise<DeleteTeamEventTypeOutput> {
|
||||
const eventType = await this.organizationsEventTypesService.deleteTeamEventType(teamId, eventTypeId);
|
||||
|
||||
|
||||
+37
-2
@@ -17,7 +17,12 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "@calcom/platform-enums";
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
} from "@calcom/platform-enums";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
@@ -276,6 +281,30 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
value: 30,
|
||||
rolling: true,
|
||||
},
|
||||
bookerLayouts: {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
},
|
||||
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
},
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -297,6 +326,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(data.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
|
||||
expect(data.offsetStart).toEqual(body.offsetStart);
|
||||
expect(data.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(data.bookerLayouts).toEqual(body.bookerLayouts);
|
||||
expect(data.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(data.requiresBookerEmailVerification).toEqual(body.requiresBookerEmailVerification);
|
||||
expect(data.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(data.lockTimeZoneToggleOnBookingPage).toEqual(body.lockTimeZoneToggleOnBookingPage);
|
||||
expect(data.color).toEqual(body.color);
|
||||
|
||||
collectiveEventType = responseBody.data;
|
||||
});
|
||||
@@ -444,7 +479,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/999999`)
|
||||
.send(body)
|
||||
.expect(404);
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should update collective event-type", async () => {
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { OutputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { DatabaseTeamEventType } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { Injectable, PipeTransform } from "@nestjs/common";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { TeamEventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class OutputTeamEventTypesResponsePipe implements PipeTransform {
|
||||
constructor(private readonly outputOrganizationsEventTypesService: OutputOrganizationsEventTypesService) {}
|
||||
|
||||
private async transformEventType(item: DatabaseTeamEventType): Promise<TeamEventTypeOutput_2024_06_14> {
|
||||
return plainToClass(
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
await this.outputOrganizationsEventTypesService.getResponseTeamEventType(item),
|
||||
{ strategy: "exposeAll" }
|
||||
);
|
||||
}
|
||||
|
||||
// Implementing function overloading to ensure correct return types based on input type:
|
||||
async transform(value: DatabaseTeamEventType[]): Promise<TeamEventTypeOutput_2024_06_14[]>;
|
||||
|
||||
async transform(value: DatabaseTeamEventType): Promise<TeamEventTypeOutput_2024_06_14>;
|
||||
|
||||
async transform(
|
||||
value: DatabaseTeamEventType | DatabaseTeamEventType[]
|
||||
): Promise<TeamEventTypeOutput_2024_06_14 | TeamEventTypeOutput_2024_06_14[]>;
|
||||
|
||||
async transform(
|
||||
value: DatabaseTeamEventType | DatabaseTeamEventType[]
|
||||
): Promise<TeamEventTypeOutput_2024_06_14 | TeamEventTypeOutput_2024_06_14[]> {
|
||||
if (Array.isArray(value)) {
|
||||
return await Promise.all(value.map((item) => this.transformEventType(item)));
|
||||
} else {
|
||||
return await this.transformEventType(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { OrganizationsOptionsAttributesController } from "@/modules/organization
|
||||
import { OrganizationsAttributesController } from "@/modules/organizations/controllers/attributes/organizations-attributes.controller";
|
||||
import { OrganizationsEventTypesController } from "@/modules/organizations/controllers/event-types/organizations-event-types.controller";
|
||||
import { OrganizationsMembershipsController } from "@/modules/organizations/controllers/memberships/organizations-membership.controller";
|
||||
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/controllers/pipes/event-types/team-event-types-response.transformer";
|
||||
import { OrganizationsSchedulesController } from "@/modules/organizations/controllers/schedules/organizations-schedules.controller";
|
||||
import { OrganizationsTeamsMembershipsController } from "@/modules/organizations/controllers/teams/memberships/organizations-teams-memberships.controller";
|
||||
import { OrganizationsTeamsController } from "@/modules/organizations/controllers/teams/organizations-teams.controller";
|
||||
@@ -79,6 +80,7 @@ import { Module } from "@nestjs/common";
|
||||
OrganizationsWebhooksService,
|
||||
WebhooksRepository,
|
||||
WebhooksService,
|
||||
OutputTeamEventTypesResponsePipe,
|
||||
],
|
||||
exports: [
|
||||
OrganizationsService,
|
||||
|
||||
+6
-6
@@ -12,7 +12,7 @@ export class OrganizationsEventTypesRepository {
|
||||
id: eventTypeId,
|
||||
teamId,
|
||||
},
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class OrganizationsEventTypesRepository {
|
||||
slug: eventTypeSlug,
|
||||
},
|
||||
},
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,21 +33,21 @@ export class OrganizationsEventTypesRepository {
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeById(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeChildren(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findMany({
|
||||
where: { parentId: eventTypeId },
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export class OrganizationsEventTypesRepository {
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
include: { users: true, schedule: true, hosts: true },
|
||||
include: { users: true, schedule: true, hosts: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_
|
||||
import { OrganizationsEventTypesRepository } from "@/modules/organizations/repositories/organizations-event-types.repository";
|
||||
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
@@ -19,37 +19,89 @@ export class InputOrganizationsEventTypesService {
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly orgEventTypesRepository: OrganizationsEventTypesRepository
|
||||
) {}
|
||||
async transformAndValidateCreateTeamEventTypeInput(
|
||||
userId: number,
|
||||
teamId: number,
|
||||
inputEventType: CreateTeamEventTypeInput_2024_06_14
|
||||
) {
|
||||
await this.validateHosts(teamId, inputEventType.hosts);
|
||||
|
||||
const transformedBody = await this.transformInputCreateTeamEventType(teamId, inputEventType);
|
||||
|
||||
await this.inputEventTypesService.validateEventTypeInputs({
|
||||
seatsPerTimeSlot: transformedBody.seatsPerTimeSlot,
|
||||
locations: transformedBody.locations,
|
||||
requiresConfirmation: transformedBody.requiresConfirmation,
|
||||
eventName: transformedBody.eventName,
|
||||
});
|
||||
|
||||
transformedBody.destinationCalendar &&
|
||||
(await this.inputEventTypesService.validateInputDestinationCalendar(
|
||||
userId,
|
||||
transformedBody.destinationCalendar
|
||||
));
|
||||
|
||||
transformedBody.useEventTypeDestinationCalendarEmail &&
|
||||
(await this.inputEventTypesService.validateInputUseDestinationCalendarEmail(userId));
|
||||
|
||||
return transformedBody;
|
||||
}
|
||||
|
||||
async transformAndValidateUpdateTeamEventTypeInput(
|
||||
userId: number,
|
||||
eventTypeId: number,
|
||||
teamId: number,
|
||||
inputEventType: UpdateTeamEventTypeInput_2024_06_14
|
||||
) {
|
||||
await this.validateHosts(teamId, inputEventType.hosts);
|
||||
|
||||
const transformedBody = await this.transformInputUpdateTeamEventType(eventTypeId, teamId, inputEventType);
|
||||
|
||||
await this.inputEventTypesService.validateEventTypeInputs({
|
||||
eventTypeId: eventTypeId,
|
||||
seatsPerTimeSlot: transformedBody.seatsPerTimeSlot,
|
||||
locations: transformedBody.locations,
|
||||
requiresConfirmation: transformedBody.requiresConfirmation,
|
||||
eventName: transformedBody.eventName,
|
||||
});
|
||||
|
||||
transformedBody.destinationCalendar &&
|
||||
(await this.inputEventTypesService.validateInputDestinationCalendar(
|
||||
userId,
|
||||
transformedBody.destinationCalendar
|
||||
));
|
||||
|
||||
transformedBody.useEventTypeDestinationCalendarEmail &&
|
||||
(await this.inputEventTypesService.validateInputUseDestinationCalendarEmail(userId));
|
||||
|
||||
return transformedBody;
|
||||
}
|
||||
|
||||
async transformInputCreateTeamEventType(
|
||||
teamId: number,
|
||||
inputEventType: CreateTeamEventTypeInput_2024_06_14
|
||||
) {
|
||||
const {
|
||||
hosts,
|
||||
assignAllTeamMembers,
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookingFields,
|
||||
recurrence,
|
||||
...rest
|
||||
} = inputEventType;
|
||||
const { hosts, assignAllTeamMembers, ...rest } = inputEventType;
|
||||
|
||||
const eventType = this.inputEventTypesService.transformInputCreateEventType(rest);
|
||||
const children = await this.getChildEventTypesForManagedEventType(null, inputEventType, teamId);
|
||||
|
||||
const metadata = rest.schedulingType === "MANAGED" ? { managedEventConfig: {} } : undefined;
|
||||
const metadata =
|
||||
rest.schedulingType === "MANAGED"
|
||||
? { managedEventConfig: {}, ...eventType.metadata }
|
||||
: eventType.metadata;
|
||||
|
||||
const teamEventType = {
|
||||
...eventType,
|
||||
hosts: assignAllTeamMembers
|
||||
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
|
||||
: this.transformInputHosts(hosts, inputEventType.schedulingType),
|
||||
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
|
||||
hosts: !(rest.schedulingType === "MANAGED")
|
||||
? assignAllTeamMembers
|
||||
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
|
||||
: this.transformInputHosts(hosts, inputEventType.schedulingType)
|
||||
: undefined,
|
||||
assignAllTeamMembers,
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookingFields,
|
||||
recurrence,
|
||||
metadata,
|
||||
children,
|
||||
};
|
||||
|
||||
return teamEventType;
|
||||
@@ -62,7 +114,7 @@ export class InputOrganizationsEventTypesService {
|
||||
) {
|
||||
const { hosts, assignAllTeamMembers, ...rest } = inputEventType;
|
||||
|
||||
const eventType = this.inputEventTypesService.transformInputUpdateEventType(rest);
|
||||
const eventType = await this.inputEventTypesService.transformInputUpdateEventType(rest, eventTypeId);
|
||||
const dbEventType = await this.orgEventTypesRepository.getTeamEventType(teamId, eventTypeId);
|
||||
|
||||
if (!dbEventType) {
|
||||
@@ -86,14 +138,16 @@ export class InputOrganizationsEventTypesService {
|
||||
}
|
||||
|
||||
async getChildEventTypesForManagedEventType(
|
||||
eventTypeId: number,
|
||||
eventTypeId: number | null,
|
||||
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
|
||||
teamId: number
|
||||
) {
|
||||
const eventType = await this.orgEventTypesRepository.getEventTypeByIdWithChildren(eventTypeId);
|
||||
|
||||
if (!eventType || eventType.schedulingType !== "MANAGED") {
|
||||
return undefined;
|
||||
let eventType = null;
|
||||
if (eventTypeId) {
|
||||
eventType = await this.orgEventTypesRepository.getEventTypeByIdWithChildren(eventTypeId);
|
||||
if (!eventType || eventType.schedulingType !== "MANAGED") {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ownersIds = await this.getOwnersIdsForManagedEventType(teamId, inputEventType, eventType);
|
||||
@@ -110,7 +164,7 @@ export class InputOrganizationsEventTypesService {
|
||||
async getOwnersIdsForManagedEventType(
|
||||
teamId: number,
|
||||
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
|
||||
eventType: { children: { userId: number | null }[] }
|
||||
eventType: { children: { userId: number | null }[] } | null
|
||||
) {
|
||||
if (inputEventType.assignAllTeamMembers) {
|
||||
return await this.organizationsTeamsRepository.getTeamMembersIds(teamId);
|
||||
@@ -122,7 +176,7 @@ export class InputOrganizationsEventTypesService {
|
||||
}
|
||||
|
||||
// note(Lauris): when API user DOES NOT update managed event type users, but we still need existing managed event type users to know which event-types to update
|
||||
return eventType.children.map((child) => child.userId).filter((id) => !!id) as number[];
|
||||
return eventType?.children.map((child) => child.userId).filter((id) => !!id) as number[];
|
||||
}
|
||||
|
||||
async getOwnersForManagedEventType(userIds: number[]) {
|
||||
@@ -170,6 +224,16 @@ export class InputOrganizationsEventTypesService {
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
async validateHosts(teamId: number, hosts: CreateTeamEventTypeInput_2024_06_14["hosts"] | undefined) {
|
||||
if (hosts && hosts.length) {
|
||||
const membersIds = await this.organizationsTeamsRepository.getTeamMembersIds(teamId);
|
||||
const invalidHosts = hosts.filter((host) => !membersIds.includes(host.userId));
|
||||
if (invalidHosts.length) {
|
||||
throw new NotFoundException(`Invalid hosts: ${invalidHosts.join(", ")}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPriorityValue(priority: keyof typeof HostPriority): number {
|
||||
|
||||
+23
-89
@@ -2,31 +2,23 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_20
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsEventTypesRepository } from "@/modules/organizations/repositories/organizations-event-types.repository";
|
||||
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
|
||||
import { InputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/input.service";
|
||||
import { OutputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { DatabaseTeamEventType } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { createEventType, updateEventType } from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
UpdateTeamEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { InputTeamEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsEventTypesService {
|
||||
constructor(
|
||||
private readonly inputService: InputOrganizationsEventTypesService,
|
||||
private readonly eventTypesService: EventTypesService_2024_06_14,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
private readonly organizationEventTypesRepository: OrganizationsEventTypesRepository,
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly outputService: OutputOrganizationsEventTypesService,
|
||||
private readonly membershipsRepository: MembershipsRepository,
|
||||
private readonly organizationsTeamsRepository: OrganizationsTeamsRepository,
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
@@ -34,24 +26,14 @@ export class OrganizationsEventTypesService {
|
||||
user: UserWithProfile,
|
||||
teamId: number,
|
||||
orgId: number,
|
||||
body: CreateTeamEventTypeInput_2024_06_14
|
||||
) {
|
||||
await this.validateHosts(teamId, body.hosts);
|
||||
body: InputTeamEventTransformed_2024_06_14
|
||||
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
|
||||
const eventTypeUser = await this.getUserToCreateTeamEvent(user, orgId);
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
hosts,
|
||||
assignAllTeamMembers,
|
||||
locations,
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookingFields,
|
||||
recurrence,
|
||||
...rest
|
||||
} = await this.inputService.transformInputCreateTeamEventType(teamId, body);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { hosts, children, destinationCalendar, ...rest } = body;
|
||||
|
||||
const { eventType: eventTypeCreated } = await createEventType({
|
||||
input: { teamId: teamId, locations, ...rest },
|
||||
input: { teamId: teamId, ...rest },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -60,31 +42,7 @@ export class OrganizationsEventTypesService {
|
||||
},
|
||||
});
|
||||
|
||||
return await this.updateTeamEventType(
|
||||
eventTypeCreated.id,
|
||||
teamId,
|
||||
{
|
||||
hosts: body.hosts,
|
||||
assignAllTeamMembers,
|
||||
bookingLimitsCount,
|
||||
bookingLimitsDuration,
|
||||
bookingWindow,
|
||||
bookingFields,
|
||||
recurrence,
|
||||
...rest,
|
||||
},
|
||||
user
|
||||
);
|
||||
}
|
||||
|
||||
async validateHosts(teamId: number, hosts: CreateTeamEventTypeInput_2024_06_14["hosts"] | undefined) {
|
||||
if (hosts && hosts.length) {
|
||||
const membersIds = await this.organizationsTeamsRepository.getTeamMembersIds(teamId);
|
||||
const invalidHosts = hosts.filter((host) => !membersIds.includes(host.userId));
|
||||
if (invalidHosts.length) {
|
||||
throw new NotFoundException(`Invalid hosts: ${invalidHosts.join(", ")}`);
|
||||
}
|
||||
}
|
||||
return this.updateTeamEventType(eventTypeCreated.id, teamId, body, user);
|
||||
}
|
||||
|
||||
async validateEventTypeExists(teamId: number, eventTypeId: number) {
|
||||
@@ -110,17 +68,17 @@ export class OrganizationsEventTypesService {
|
||||
};
|
||||
}
|
||||
|
||||
async getTeamEventType(teamId: number, eventTypeId: number) {
|
||||
async getTeamEventType(teamId: number, eventTypeId: number): Promise<DatabaseTeamEventType | null> {
|
||||
const eventType = await this.organizationEventTypesRepository.getTeamEventType(teamId, eventTypeId);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.outputService.getResponseTeamEventType(eventType);
|
||||
return eventType;
|
||||
}
|
||||
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string) {
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string): Promise<DatabaseTeamEventType | null> {
|
||||
const eventType = await this.organizationEventTypesRepository.getTeamEventTypeBySlug(
|
||||
teamId,
|
||||
eventTypeSlug
|
||||
@@ -130,46 +88,28 @@ export class OrganizationsEventTypesService {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.outputService.getResponseTeamEventType(eventType);
|
||||
return eventType;
|
||||
}
|
||||
|
||||
async getTeamEventTypes(teamId: number) {
|
||||
const eventTypes = await this.organizationEventTypesRepository.getTeamEventTypes(teamId);
|
||||
|
||||
const eventTypePromises = eventTypes.map(async (eventType) => {
|
||||
return await this.outputService.getResponseTeamEventType(eventType);
|
||||
});
|
||||
|
||||
return await Promise.all(eventTypePromises);
|
||||
async getTeamEventTypes(teamId: number): Promise<DatabaseTeamEventType[]> {
|
||||
return await this.organizationEventTypesRepository.getTeamEventTypes(teamId);
|
||||
}
|
||||
|
||||
async getTeamsEventTypes(orgId: number, skip = 0, take = 250) {
|
||||
const eventTypes = await this.organizationEventTypesRepository.getTeamsEventTypes(orgId, skip, take);
|
||||
|
||||
const eventTypePromises = eventTypes.map(async (eventType) => {
|
||||
return await this.outputService.getResponseTeamEventType(eventType);
|
||||
});
|
||||
|
||||
return await Promise.all(eventTypePromises);
|
||||
async getTeamsEventTypes(orgId: number, skip = 0, take = 250): Promise<DatabaseTeamEventType[]> {
|
||||
return await this.organizationEventTypesRepository.getTeamsEventTypes(orgId, skip, take);
|
||||
}
|
||||
|
||||
async updateTeamEventType(
|
||||
eventTypeId: number,
|
||||
teamId: number,
|
||||
body: UpdateTeamEventTypeInput_2024_06_14,
|
||||
body: InputTeamEventTransformed_2024_06_14,
|
||||
user: UserWithProfile
|
||||
) {
|
||||
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
|
||||
await this.validateEventTypeExists(teamId, eventTypeId);
|
||||
await this.validateHosts(teamId, body.hosts);
|
||||
const eventTypeUser = await this.eventTypesService.getUserToUpdateEvent(user);
|
||||
const bodyTransformed = await this.inputService.transformInputUpdateTeamEventType(
|
||||
eventTypeId,
|
||||
teamId,
|
||||
body
|
||||
);
|
||||
|
||||
await updateEventType({
|
||||
input: { id: eventTypeId, ...bodyTransformed },
|
||||
input: { id: eventTypeId, ...body },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -185,18 +125,12 @@ export class OrganizationsEventTypesService {
|
||||
}
|
||||
|
||||
if (eventType.schedulingType !== "MANAGED") {
|
||||
return this.outputService.getResponseTeamEventType(eventType);
|
||||
return eventType;
|
||||
}
|
||||
|
||||
const children = await this.organizationEventTypesRepository.getEventTypeChildren(eventType.id);
|
||||
const childrenEventTypes = await this.organizationEventTypesRepository.getEventTypeChildren(eventType.id);
|
||||
|
||||
const eventTypes = [eventType, ...children];
|
||||
|
||||
const eventTypePromises = eventTypes.map(async (e) => {
|
||||
return await this.outputService.getResponseTeamEventType(e);
|
||||
});
|
||||
|
||||
return await Promise.all(eventTypePromises);
|
||||
return [eventType, ...childrenEventTypes];
|
||||
}
|
||||
|
||||
async deleteTeamEventType(teamId: number, eventTypeId: number) {
|
||||
|
||||
@@ -2,16 +2,21 @@ import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types
|
||||
import { OrganizationsEventTypesRepository } from "@/modules/organizations/repositories/organizations-event-types.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import type { EventType, User, Schedule, Host } from "@prisma/client";
|
||||
import type { EventType, User, Schedule, Host, DestinationCalendar } from "@prisma/client";
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
|
||||
import { HostPriority, TeamEventTypeResponseHost } from "@calcom/platform-types";
|
||||
|
||||
type EventTypeRelations = { users: User[]; schedule: Schedule | null; hosts: Host[] };
|
||||
type DatabaseEventType = EventType & EventTypeRelations;
|
||||
type EventTypeRelations = {
|
||||
users: User[];
|
||||
schedule: Schedule | null;
|
||||
hosts: Host[];
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
};
|
||||
export type DatabaseTeamEventType = EventType & EventTypeRelations;
|
||||
|
||||
type Input = Pick<
|
||||
DatabaseEventType,
|
||||
DatabaseTeamEventType,
|
||||
| "id"
|
||||
| "length"
|
||||
| "title"
|
||||
@@ -52,6 +57,14 @@ type Input = Pick<
|
||||
| "periodCountCalendarDays"
|
||||
| "periodStartDate"
|
||||
| "periodEndDate"
|
||||
| "requiresBookerEmailVerification"
|
||||
| "hideCalendarNotes"
|
||||
| "lockTimeZoneToggleOnBookingPage"
|
||||
| "eventTypeColor"
|
||||
| "seatsShowAttendees"
|
||||
| "requiresConfirmationWillBlockSlot"
|
||||
| "eventName"
|
||||
| "useEventTypeDestinationCalendarEmail"
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
@@ -65,7 +78,7 @@ export class OutputOrganizationsEventTypesService {
|
||||
async getResponseTeamEventType(databaseEventType: Input) {
|
||||
const { teamId, userId, parentId, assignAllTeamMembers } = databaseEventType;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { ownerId, users, ...rest } = await this.outputEventTypesService.getResponseEventType(
|
||||
const { ownerId, users, ...rest } = this.outputEventTypesService.getResponseEventType(
|
||||
0,
|
||||
databaseEventType
|
||||
);
|
||||
@@ -80,6 +93,7 @@ export class OutputOrganizationsEventTypesService {
|
||||
teamId,
|
||||
ownerId: userId,
|
||||
parentEventTypeId: parentId,
|
||||
schedulingType: databaseEventType.schedulingType,
|
||||
assignAllTeamMembers: teamId ? assignAllTeamMembers : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4923,6 +4923,11 @@
|
||||
"nullable": true,
|
||||
"example": "alice"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "alice"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string",
|
||||
"example": "America/New_York"
|
||||
@@ -4998,6 +5003,7 @@
|
||||
"id",
|
||||
"email",
|
||||
"username",
|
||||
"name",
|
||||
"timeZone",
|
||||
"weekStart",
|
||||
"createdDate",
|
||||
@@ -6079,94 +6085,6 @@
|
||||
"value"
|
||||
]
|
||||
},
|
||||
"BookingLimitsCount_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "number",
|
||||
"description": "The number of bookings per day",
|
||||
"example": 1
|
||||
},
|
||||
"week": {
|
||||
"type": "number",
|
||||
"description": "The number of bookings per week",
|
||||
"example": 2
|
||||
},
|
||||
"month": {
|
||||
"type": "number",
|
||||
"description": "The number of bookings per month",
|
||||
"example": 3
|
||||
},
|
||||
"year": {
|
||||
"type": "number",
|
||||
"description": "The number of bookings per year",
|
||||
"example": 4
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"year"
|
||||
]
|
||||
},
|
||||
"BookingLimitsDuration_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per day (must be a multiple of 15)",
|
||||
"example": 60
|
||||
},
|
||||
"week": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per week (must be a multiple of 15)",
|
||||
"example": 120
|
||||
},
|
||||
"month": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per month (must be a multiple of 15)",
|
||||
"example": 180
|
||||
},
|
||||
"year": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per year (must be a multiple of 15)",
|
||||
"example": 240
|
||||
}
|
||||
}
|
||||
},
|
||||
"Recurrence_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"example": 10,
|
||||
"description": "Repeats every {count} week | month | year"
|
||||
},
|
||||
"occurrences": {
|
||||
"type": "number",
|
||||
"example": 10,
|
||||
"description": "Repeats for a maximum of {count} events"
|
||||
},
|
||||
"frequency": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yearly",
|
||||
"monthly",
|
||||
"weekly"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"interval",
|
||||
"occurrences",
|
||||
"frequency"
|
||||
]
|
||||
},
|
||||
"CreateEventTypeInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6272,24 +6190,16 @@
|
||||
"description": "If you want that this event has different schedule than user's default one you can specify it here."
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"description": "Limit how many times this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit how many times this event can be booked"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean",
|
||||
"description": "This will limit your availability for this event type to one slot per day, scheduled at the earliest available time."
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"description": "Limit total amount of time that this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit total amount of time that this event can be booked"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -6312,13 +6222,10 @@
|
||||
"type": "number",
|
||||
"description": "Offset timeslots shown to bookers by a specified number of minutes"
|
||||
},
|
||||
"recurrence": {
|
||||
"description": "Create a recurring event that can be booked once but will occur multiple times",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Recurrence_2024_06_14"
|
||||
}
|
||||
]
|
||||
"customName": {
|
||||
"type": "string",
|
||||
"description": "Customizable event name with valid variables: \n {Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name}, \n {Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION}, \n {HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}",
|
||||
"example": "{Event type title} between {Organiser} and {Scheduler}"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6339,7 +6246,7 @@
|
||||
"bookingLimitsDuration",
|
||||
"bookingWindow",
|
||||
"offsetStart",
|
||||
"recurrence"
|
||||
"customName"
|
||||
]
|
||||
},
|
||||
"EmailDefaultFieldOutput_2024_06_14": {
|
||||
@@ -7136,6 +7043,104 @@
|
||||
"isDefault"
|
||||
]
|
||||
},
|
||||
"BaseBookingLimitsDuration_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per day (must be a multiple of 15)",
|
||||
"example": 60
|
||||
},
|
||||
"week": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per week (must be a multiple of 15)",
|
||||
"example": 120
|
||||
},
|
||||
"month": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per month (must be a multiple of 15)",
|
||||
"example": 180
|
||||
},
|
||||
"year": {
|
||||
"type": "number",
|
||||
"minimum": 15,
|
||||
"description": "The duration of bookings per year (must be a multiple of 15)",
|
||||
"example": 240
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"BookerLayouts_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"EventTypeColor_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lightThemeHex": {
|
||||
"type": "string",
|
||||
"description": "Color used for event types in light theme",
|
||||
"example": "#292929"
|
||||
},
|
||||
"darkThemeHex": {
|
||||
"type": "string",
|
||||
"description": "Color used for event types in dark theme",
|
||||
"example": "#fafafa"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lightThemeHex",
|
||||
"darkThemeHex"
|
||||
]
|
||||
},
|
||||
"Seats_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"seatsPerTimeSlot": {
|
||||
"type": "number",
|
||||
"description": "Number of seats available per time slot",
|
||||
"example": 4
|
||||
},
|
||||
"showAttendeeInfo": {
|
||||
"type": "boolean",
|
||||
"description": "Show attendee information to other guests",
|
||||
"example": true
|
||||
},
|
||||
"showAvailabilityCount": {
|
||||
"type": "boolean",
|
||||
"description": "Display the count of available seats",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"seatsPerTimeSlot",
|
||||
"showAttendeeInfo",
|
||||
"showAvailabilityCount"
|
||||
]
|
||||
},
|
||||
"DestinationCalendar_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"integration": {
|
||||
"type": "string",
|
||||
"description": "The integration type of the destination calendar. Refer to the /api/v2/calendars endpoint to retrieve the integration type of your connected calendars."
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"description": "The external ID of the destination calendar. Refer to the /api/v2/calendars endpoint to retrieve the external IDs of your connected calendars."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"integration",
|
||||
"externalId"
|
||||
]
|
||||
},
|
||||
"EventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7157,7 +7162,7 @@
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Discover the culinary wonders of the Argentina by making the best flan ever!"
|
||||
"example": "Discover the culinary wonders of Argentina by making the best flan ever!"
|
||||
},
|
||||
"locations": {
|
||||
"type": "array",
|
||||
@@ -7264,9 +7269,6 @@
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"requiresConfirmation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -7285,6 +7287,9 @@
|
||||
"successRedirectUrl": {
|
||||
"type": "object"
|
||||
},
|
||||
"isInstantEvent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"seatsShowAvailabilityCount": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -7292,13 +7297,13 @@
|
||||
"type": "object"
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
"type": "object"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
"type": "object"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -7317,9 +7322,36 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"bookerLayouts": {
|
||||
"$ref": "#/components/schemas/BookerLayouts_2024_06_14"
|
||||
},
|
||||
"confirmationPolicy": {
|
||||
"type": "object"
|
||||
},
|
||||
"requiresBookerEmailVerification": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hideCalendarNotes": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"color": {
|
||||
"$ref": "#/components/schemas/EventTypeColor_2024_06_14"
|
||||
},
|
||||
"seats": {
|
||||
"$ref": "#/components/schemas/Seats_2024_06_14"
|
||||
},
|
||||
"offsetStart": {
|
||||
"type": "number"
|
||||
},
|
||||
"customName": {
|
||||
"type": "string"
|
||||
},
|
||||
"destinationCalendar": {
|
||||
"$ref": "#/components/schemas/DestinationCalendar_2024_06_14"
|
||||
},
|
||||
"useDestinationCalendarEmail": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ownerId": {
|
||||
"type": "number",
|
||||
"example": 10
|
||||
@@ -7346,20 +7378,29 @@
|
||||
"afterEventBuffer",
|
||||
"recurrence",
|
||||
"metadata",
|
||||
"requiresConfirmation",
|
||||
"price",
|
||||
"currency",
|
||||
"lockTimeZoneToggleOnBookingPage",
|
||||
"seatsPerTimeSlot",
|
||||
"forwardParamsSuccessRedirect",
|
||||
"successRedirectUrl",
|
||||
"isInstantEvent",
|
||||
"seatsShowAvailabilityCount",
|
||||
"scheduleId",
|
||||
"bookingLimitsCount",
|
||||
"onlyShowFirstAvailableSlot",
|
||||
"bookingLimitsDuration",
|
||||
"bookingWindow",
|
||||
"bookerLayouts",
|
||||
"confirmationPolicy",
|
||||
"requiresBookerEmailVerification",
|
||||
"hideCalendarNotes",
|
||||
"color",
|
||||
"seats",
|
||||
"offsetStart",
|
||||
"customName",
|
||||
"destinationCalendar",
|
||||
"useDestinationCalendarEmail",
|
||||
"ownerId",
|
||||
"users"
|
||||
]
|
||||
@@ -7537,24 +7578,16 @@
|
||||
"description": "If you want that this event has different schedule than user's default one you can specify it here."
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"description": "Limit how many times this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit how many times this event can be booked"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean",
|
||||
"description": "This will limit your availability for this event type to one slot per day, scheduled at the earliest available time."
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"description": "Limit total amount of time that this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit total amount of time that this event can be booked"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -7577,13 +7610,10 @@
|
||||
"type": "number",
|
||||
"description": "Offset timeslots shown to bookers by a specified number of minutes"
|
||||
},
|
||||
"recurrence": {
|
||||
"description": "Create a recurring event that can be booked once but will occur multiple times",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Recurrence_2024_06_14"
|
||||
}
|
||||
]
|
||||
"customName": {
|
||||
"type": "string",
|
||||
"description": "Customizable event name with valid variables: \n {Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name}, \n {Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION}, \n {HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}",
|
||||
"example": "{Event type title} between {Organiser} and {Scheduler}"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -7604,7 +7634,7 @@
|
||||
"bookingLimitsDuration",
|
||||
"bookingWindow",
|
||||
"offsetStart",
|
||||
"recurrence"
|
||||
"customName"
|
||||
]
|
||||
},
|
||||
"UpdateEventTypeOutput_2024_06_14": {
|
||||
@@ -7938,6 +7968,9 @@
|
||||
"weekStart": {
|
||||
"type": "string",
|
||||
"default": "Sunday"
|
||||
},
|
||||
"bookingLimits": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8930,24 +8963,16 @@
|
||||
"description": "If you want that this event has different schedule than user's default one you can specify it here."
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"description": "Limit how many times this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit how many times this event can be booked"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean",
|
||||
"description": "This will limit your availability for this event type to one slot per day, scheduled at the earliest available time."
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"description": "Limit total amount of time that this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit total amount of time that this event can be booked"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -8970,13 +8995,10 @@
|
||||
"type": "number",
|
||||
"description": "Offset timeslots shown to bookers by a specified number of minutes"
|
||||
},
|
||||
"recurrence": {
|
||||
"description": "Create a recurring event that can be booked once but will occur multiple times",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Recurrence_2024_06_14"
|
||||
}
|
||||
]
|
||||
"customName": {
|
||||
"type": "string",
|
||||
"description": "Customizable event name with valid variables: \n {Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name}, \n {Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION}, \n {HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}",
|
||||
"example": "{Event type title} between {Organiser} and {Scheduler}"
|
||||
},
|
||||
"schedulingType": {
|
||||
"type": "object"
|
||||
@@ -9010,7 +9032,7 @@
|
||||
"bookingLimitsDuration",
|
||||
"bookingWindow",
|
||||
"offsetStart",
|
||||
"recurrence",
|
||||
"customName",
|
||||
"schedulingType",
|
||||
"hosts",
|
||||
"assignAllTeamMembers"
|
||||
@@ -9046,6 +9068,38 @@
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"Recurrence_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"example": 10,
|
||||
"description": "Repeats every {count} week | month | year"
|
||||
},
|
||||
"occurrences": {
|
||||
"type": "number",
|
||||
"example": 10,
|
||||
"description": "Repeats for a maximum of {count} events"
|
||||
},
|
||||
"frequency": {
|
||||
"enum": [
|
||||
"yearly",
|
||||
"monthly",
|
||||
"weekly"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"interval",
|
||||
"occurrences",
|
||||
"frequency"
|
||||
]
|
||||
},
|
||||
"TeamEventTypeResponseHost": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9101,7 +9155,7 @@
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Discover the culinary wonders of the Argentina by making the best flan ever!"
|
||||
"example": "Discover the culinary wonders of Argentina by making the best flan ever!"
|
||||
},
|
||||
"locations": {
|
||||
"type": "array",
|
||||
@@ -9215,9 +9269,6 @@
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"requiresConfirmation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -9239,6 +9290,9 @@
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"isInstantEvent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"seatsShowAvailabilityCount": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
@@ -9248,13 +9302,13 @@
|
||||
"nullable": true
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
"type": "object"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
"type": "object"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -9273,18 +9327,36 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"bookerLayouts": {
|
||||
"$ref": "#/components/schemas/BookerLayouts_2024_06_14"
|
||||
},
|
||||
"confirmationPolicy": {
|
||||
"type": "object"
|
||||
},
|
||||
"requiresBookerEmailVerification": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hideCalendarNotes": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"color": {
|
||||
"$ref": "#/components/schemas/EventTypeColor_2024_06_14"
|
||||
},
|
||||
"seats": {
|
||||
"$ref": "#/components/schemas/Seats_2024_06_14"
|
||||
},
|
||||
"offsetStart": {
|
||||
"type": "number",
|
||||
"minimum": 1
|
||||
},
|
||||
"schedulingType": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"enum": [
|
||||
"ROUND_ROBIN",
|
||||
"COLLECTIVE",
|
||||
"MANAGED"
|
||||
]
|
||||
"customName": {
|
||||
"type": "string"
|
||||
},
|
||||
"destinationCalendar": {
|
||||
"$ref": "#/components/schemas/DestinationCalendar_2024_06_14"
|
||||
},
|
||||
"useDestinationCalendarEmail": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"teamId": {
|
||||
"type": "number",
|
||||
@@ -9297,7 +9369,7 @@
|
||||
"parentEventTypeId": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "For managed event types parent event type is the event type that this event type is based on"
|
||||
"description": "For managed event types, parent event type is the event type that this event type is based on"
|
||||
},
|
||||
"hosts": {
|
||||
"type": "array",
|
||||
@@ -9307,6 +9379,15 @@
|
||||
},
|
||||
"assignAllTeamMembers": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"schedulingType": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"enum": [
|
||||
"ROUND_ROBIN",
|
||||
"COLLECTIVE",
|
||||
"MANAGED"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -9320,17 +9401,15 @@
|
||||
"disableGuests",
|
||||
"recurrence",
|
||||
"metadata",
|
||||
"requiresConfirmation",
|
||||
"price",
|
||||
"currency",
|
||||
"lockTimeZoneToggleOnBookingPage",
|
||||
"seatsPerTimeSlot",
|
||||
"forwardParamsSuccessRedirect",
|
||||
"successRedirectUrl",
|
||||
"seatsShowAvailabilityCount",
|
||||
"isInstantEvent",
|
||||
"scheduleId",
|
||||
"schedulingType",
|
||||
"hosts"
|
||||
"hosts",
|
||||
"schedulingType"
|
||||
]
|
||||
},
|
||||
"GetTeamEventTypeOutput": {
|
||||
@@ -9580,24 +9659,16 @@
|
||||
"description": "If you want that this event has different schedule than user's default one you can specify it here."
|
||||
},
|
||||
"bookingLimitsCount": {
|
||||
"description": "Limit how many times this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit how many times this event can be booked"
|
||||
},
|
||||
"onlyShowFirstAvailableSlot": {
|
||||
"type": "boolean",
|
||||
"description": "This will limit your availability for this event type to one slot per day, scheduled at the earliest available time."
|
||||
},
|
||||
"bookingLimitsDuration": {
|
||||
"description": "Limit total amount of time that this event can be booked",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
|
||||
}
|
||||
]
|
||||
"type": "object",
|
||||
"description": "Limit total amount of time that this event can be booked"
|
||||
},
|
||||
"bookingWindow": {
|
||||
"type": "array",
|
||||
@@ -9620,13 +9691,10 @@
|
||||
"type": "number",
|
||||
"description": "Offset timeslots shown to bookers by a specified number of minutes"
|
||||
},
|
||||
"recurrence": {
|
||||
"description": "Create a recurring event that can be booked once but will occur multiple times",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Recurrence_2024_06_14"
|
||||
}
|
||||
]
|
||||
"customName": {
|
||||
"type": "string",
|
||||
"description": "Customizable event name with valid variables: \n {Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name}, \n {Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION}, \n {HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}",
|
||||
"example": "{Event type title} between {Organiser} and {Scheduler}"
|
||||
},
|
||||
"hosts": {
|
||||
"type": "array",
|
||||
@@ -9657,7 +9725,7 @@
|
||||
"bookingLimitsDuration",
|
||||
"bookingWindow",
|
||||
"offsetStart",
|
||||
"recurrence",
|
||||
"customName",
|
||||
"hosts",
|
||||
"assignAllTeamMembers"
|
||||
]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { apiLogin } from "playwright/fixtures/users";
|
||||
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { MembershipRole } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { apiLogin } from "./fixtures/users";
|
||||
import { test } from "./lib/fixtures";
|
||||
import {
|
||||
bookTimeSlot,
|
||||
|
||||
@@ -332,7 +332,7 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
|
||||
},
|
||||
body: JSON.stringify(await translateEvent(event)),
|
||||
});
|
||||
|
||||
|
||||
const updatedMeeting = await fetchZoomApi(`meetings/${bookingRef.uid}`);
|
||||
const result = zoomEventResultSchema.parse(updatedMeeting);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useFormContext } from "react-hook-form";
|
||||
import z from "zod";
|
||||
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { useLastUsed, LastUsed } from "@calcom/lib/hooks/useLastUsed";
|
||||
import { useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui";
|
||||
@@ -38,7 +38,7 @@ export function SAMLLogin({ samlTenantID, samlProductID, setErrorMessage }: Prop
|
||||
<Button
|
||||
color="minimal"
|
||||
data-testid="saml"
|
||||
className="text-brand-500 font-medium p-0 h-auto"
|
||||
className="text-brand-500 h-auto p-0 font-medium"
|
||||
onClick={async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { FrequencyInput } from "@calcom/platform-enums/monorepo";
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
BookerLayoutsOutputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
FrequencyInput,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
InputBookingField_2024_06_14,
|
||||
Location_2024_06_14,
|
||||
BookingLimitsCount_2024_06_14,
|
||||
BookingWindow_2024_06_14,
|
||||
BookerLayouts_2024_06_14,
|
||||
ConfirmationPolicy_2024_06_14,
|
||||
EventTypeColor_2024_06_14,
|
||||
Recurrence_2024_06_14,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
SeatOptionsTransformedSchema,
|
||||
SeatOptionsDisabledSchema,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { CustomField } from "../internal-to-api/booking-fields";
|
||||
@@ -17,6 +29,10 @@ import {
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
transformSeatsApiToInternal,
|
||||
transformEventColorsApiToInternal,
|
||||
transformBookerLayoutsApiToInternal,
|
||||
transformConfirmationPolicyApiToInternal,
|
||||
} from "./index";
|
||||
|
||||
describe("transformLocationsApiToInternal", () => {
|
||||
@@ -585,6 +601,152 @@ describe("transformFutureBookingLimitsApiToInternal", () => {
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform disabled", () => {
|
||||
const input: BookingWindow_2024_06_14 = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
periodType: "UNLIMITED",
|
||||
};
|
||||
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformBookerLayoutsApiToInternal", () => {
|
||||
it("should transform booker layouts", () => {
|
||||
const input: BookerLayouts_2024_06_14 = {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.week,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsOutputEnum_2024_06_14.column_view,
|
||||
BookerLayoutsOutputEnum_2024_06_14.month_view,
|
||||
BookerLayoutsOutputEnum_2024_06_14.week_view,
|
||||
],
|
||||
defaultLayout: BookerLayoutsOutputEnum_2024_06_14.week_view,
|
||||
};
|
||||
const result = transformBookerLayoutsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformConfirmationPolicyApiToInternal", () => {
|
||||
it("should transform requires confirmation - time", () => {
|
||||
const input: ConfirmationPolicy_2024_06_14 = {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
requiresConfirmationWillBlockSlot: true,
|
||||
};
|
||||
const result = transformConfirmationPolicyApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform requires confirmation - always", () => {
|
||||
const input: ConfirmationPolicy_2024_06_14 = {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationWillBlockSlot: true,
|
||||
};
|
||||
const result = transformConfirmationPolicyApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform requires confirmation - disabled", () => {
|
||||
const input: ConfirmationPolicy_2024_06_14 = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
requiresConfirmation: false,
|
||||
requiresConfirmationWillBlockSlot: false,
|
||||
requiresConfirmationThreshold: undefined,
|
||||
};
|
||||
const result = transformConfirmationPolicyApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformEventColorsApiToInternal", () => {
|
||||
it("should transform event type colors", () => {
|
||||
const input: EventTypeColor_2024_06_14 = {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
darkEventTypeColor: "#292929",
|
||||
lightEventTypeColor: "#fafafa",
|
||||
};
|
||||
|
||||
const result = transformEventColorsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformSeatsApiToInternal", () => {
|
||||
it("should transform seat options", () => {
|
||||
const input: CreateEventTypeInput_2024_06_14["seats"] = {
|
||||
seatsPerTimeSlot: 20,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: false,
|
||||
};
|
||||
|
||||
const expectedOutput: SeatOptionsTransformedSchema = {
|
||||
seatsPerTimeSlot: 20,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
};
|
||||
|
||||
const result = transformSeatsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform seat options - disabled", () => {
|
||||
const input: CreateEventTypeInput_2024_06_14["seats"] = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const expectedOutput: SeatOptionsDisabledSchema = {
|
||||
seatsPerTimeSlot: null,
|
||||
};
|
||||
|
||||
const result = transformSeatsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformRecurrenceApiToInternal", () => {
|
||||
@@ -600,7 +762,15 @@ describe("transformRecurrenceApiToInternal", () => {
|
||||
freq: 2,
|
||||
};
|
||||
const result = transformRecurrenceApiToInternal(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform recurrence - disabled", () => {
|
||||
const input: CreateEventTypeInput_2024_06_14["recurrence"] = {
|
||||
disabled: true,
|
||||
};
|
||||
const expectedOutput = undefined;
|
||||
const result = transformRecurrenceApiToInternal(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
BookerLayoutsOutputEnum_2024_06_14,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import { type CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export function transformBookerLayoutsApiToInternal(
|
||||
inputBookerLayout: CreateEventTypeInput_2024_06_14["bookerLayouts"]
|
||||
) {
|
||||
if (!inputBookerLayout) return undefined;
|
||||
|
||||
const inputToOutputMap = {
|
||||
[BookerLayoutsInputEnum_2024_06_14.month]: BookerLayoutsOutputEnum_2024_06_14.month_view,
|
||||
[BookerLayoutsInputEnum_2024_06_14.week]: BookerLayoutsOutputEnum_2024_06_14.week_view,
|
||||
[BookerLayoutsInputEnum_2024_06_14.column]: BookerLayoutsOutputEnum_2024_06_14.column_view,
|
||||
};
|
||||
|
||||
return {
|
||||
defaultLayout: inputToOutputMap[inputBookerLayout.defaultLayout],
|
||||
enabledLayouts: inputBookerLayout.enabledLayouts.map((layout) => inputToOutputMap[layout]),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ConfirmationPolicyEnum } from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
ConfirmationPolicyTransformedSchema,
|
||||
NoticeThresholdTransformedSchema,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformConfirmationPolicyApiToInternal(
|
||||
inputConfirmationPolicy: CreateEventTypeInput_2024_06_14["confirmationPolicy"]
|
||||
): ConfirmationPolicyTransformedSchema | undefined {
|
||||
if (!inputConfirmationPolicy) return undefined;
|
||||
if (inputConfirmationPolicy.disabled) {
|
||||
return {
|
||||
requiresConfirmation: false,
|
||||
requiresConfirmationWillBlockSlot: false,
|
||||
requiresConfirmationThreshold: undefined,
|
||||
};
|
||||
}
|
||||
switch (inputConfirmationPolicy.type) {
|
||||
case ConfirmationPolicyEnum.ALWAYS:
|
||||
return {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationThreshold: undefined,
|
||||
requiresConfirmationWillBlockSlot: inputConfirmationPolicy.blockUnconfirmedBookingsInBooker,
|
||||
};
|
||||
case ConfirmationPolicyEnum.TIME:
|
||||
return {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationThreshold: {
|
||||
unit: inputConfirmationPolicy.noticeThreshold?.unit,
|
||||
time: inputConfirmationPolicy.noticeThreshold?.count,
|
||||
} as NoticeThresholdTransformedSchema,
|
||||
requiresConfirmationWillBlockSlot: inputConfirmationPolicy.blockUnconfirmedBookingsInBooker,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeColorsTransformedSchema,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformEventColorsApiToInternal(
|
||||
inputEventTypeColors: CreateEventTypeInput_2024_06_14["color"]
|
||||
): EventTypeColorsTransformedSchema | undefined {
|
||||
if (!inputEventTypeColors) return undefined;
|
||||
|
||||
return {
|
||||
darkEventTypeColor: inputEventTypeColors.darkThemeHex,
|
||||
lightEventTypeColor: inputEventTypeColors.lightThemeHex,
|
||||
};
|
||||
}
|
||||
@@ -12,6 +12,14 @@ import {
|
||||
export function transformFutureBookingLimitsApiToInternal(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingWindow"]
|
||||
): TransformFutureBookingsLimitSchema_2024_06_14 | undefined {
|
||||
if (!inputBookingLimits) {
|
||||
return;
|
||||
}
|
||||
if (inputBookingLimits.disabled) {
|
||||
return {
|
||||
periodType: BookingWindowPeriodOutputTypeEnum_2024_06_14.UNLIMITED,
|
||||
};
|
||||
}
|
||||
switch (inputBookingLimits?.type) {
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
|
||||
return {
|
||||
|
||||
@@ -3,3 +3,7 @@ export * from "./locations";
|
||||
export * from "./recurrence";
|
||||
export * from "./interval-limits";
|
||||
export * from "./future-booking-limits";
|
||||
export * from "./booker-layouts";
|
||||
export * from "./confirmation-policy";
|
||||
export * from "./event-colors";
|
||||
export * from "./seats";
|
||||
|
||||
@@ -9,6 +9,9 @@ export function transformIntervalLimitsApiToInternal(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]
|
||||
) {
|
||||
const res: TransformBookingLimitsSchema_2024_06_14 = {};
|
||||
if (inputBookingLimits?.disabled) {
|
||||
return res;
|
||||
}
|
||||
inputBookingLimits &&
|
||||
Object.entries(inputBookingLimits).map(([key, value]) => {
|
||||
const outputKey: BookingLimitsKeyOutputType_2024_06_14 = BookingLimitsEnum_2024_06_14[
|
||||
|
||||
@@ -71,7 +71,7 @@ const UserPhoneSchema = z.object({
|
||||
|
||||
type InPersonLocation = z.infer<typeof InPersonSchema>;
|
||||
type LinkLocation = z.infer<typeof LinkSchema>;
|
||||
type IntegrationLocation = z.infer<typeof IntegrationSchema>;
|
||||
export type IntegrationLocation = z.infer<typeof IntegrationSchema>;
|
||||
type UserPhoneLocation = z.infer<typeof UserPhoneSchema>;
|
||||
|
||||
const TransformedLocationSchema = z.union([InPersonSchema, LinkSchema, IntegrationSchema, UserPhoneSchema]);
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
export function transformRecurrenceApiToInternal(
|
||||
recurrence: CreateEventTypeInput_2024_06_14["recurrence"]
|
||||
): TransformRecurringEventSchema_2024_06_14 | undefined {
|
||||
if (!recurrence) return undefined;
|
||||
if (!recurrence || recurrence.disabled) return undefined;
|
||||
|
||||
return {
|
||||
interval: recurrence.interval,
|
||||
count: recurrence.occurrences,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
SeatOptionsTransformedSchema,
|
||||
SeatOptionsDisabledSchema,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformSeatsApiToInternal(
|
||||
inputSeats: CreateEventTypeInput_2024_06_14["seats"]
|
||||
): SeatOptionsTransformedSchema | SeatOptionsDisabledSchema | undefined {
|
||||
if (!inputSeats) return undefined;
|
||||
|
||||
if (inputSeats.disabled)
|
||||
return {
|
||||
seatsPerTimeSlot: null,
|
||||
};
|
||||
|
||||
return {
|
||||
seatsPerTimeSlot: inputSeats.seatsPerTimeSlot,
|
||||
seatsShowAttendees: inputSeats.showAttendeeInfo,
|
||||
seatsShowAvailabilityCount: inputSeats.showAvailabilityCount,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
BookerLayoutsOutputEnum_2024_06_14,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import type { BookerLayoutsTransformedSchema } from "@calcom/platform-types";
|
||||
|
||||
export function transformBookerLayoutsInternalToApi(
|
||||
transformedBookerLayouts: BookerLayoutsTransformedSchema
|
||||
) {
|
||||
const outputToInputMap = {
|
||||
[BookerLayoutsOutputEnum_2024_06_14.month_view]: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
[BookerLayoutsOutputEnum_2024_06_14.week_view]: BookerLayoutsInputEnum_2024_06_14.week,
|
||||
[BookerLayoutsOutputEnum_2024_06_14.column_view]: BookerLayoutsInputEnum_2024_06_14.column,
|
||||
};
|
||||
|
||||
return {
|
||||
defaultLayout: outputToInputMap[transformedBookerLayouts.defaultLayout],
|
||||
enabledLayouts: transformedBookerLayouts.enabledLayouts.map((layout) => outputToInputMap[layout]),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { EventTypeColorsTransformedSchema, EventTypeColor_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export function transformEventTypeColorsInternalToApi(
|
||||
transformedColors: EventTypeColorsTransformedSchema
|
||||
): EventTypeColor_2024_06_14 {
|
||||
return {
|
||||
darkThemeHex: transformedColors.darkEventTypeColor,
|
||||
lightThemeHex: transformedColors.lightEventTypeColor,
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
RangeWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
Disabled_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformFutureBookingLimitsInternalToApi(
|
||||
@@ -38,6 +39,11 @@ export function transformFutureBookingLimitsInternalToApi(
|
||||
value: transformedFutureBookingsLimitsFields.periodDays,
|
||||
rolling: false,
|
||||
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
|
||||
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.UNLIMITED:
|
||||
return {
|
||||
disabled: true,
|
||||
} as Disabled_2024_06_14;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -3,3 +3,7 @@ export * from "./locations";
|
||||
export * from "./recurrence";
|
||||
export * from "./interval-limits";
|
||||
export * from "./future-booking-limits";
|
||||
export * from "./booker-layouts";
|
||||
export * from "./event-type-colors";
|
||||
export * from "./requires-confirmation";
|
||||
export * from "./seats";
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
BookerLayoutsOutputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
AddressLocation_2024_06_14,
|
||||
LinkLocation_2024_06_14,
|
||||
@@ -7,7 +13,11 @@ import type {
|
||||
IntegrationLocation_2024_06_14,
|
||||
TransformBookingLimitsSchema_2024_06_14,
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
BookerLayoutsTransformedSchema,
|
||||
EventTypeColorsTransformedSchema,
|
||||
TransformRecurringEventSchema_2024_06_14,
|
||||
SeatOptionsTransformedSchema,
|
||||
SeatOptionsDisabledSchema,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import {
|
||||
@@ -16,6 +26,10 @@ import {
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
transformBookerLayoutsInternalToApi,
|
||||
transformRequiresConfirmationInternalToApi,
|
||||
transformEventTypeColorsInternalToApi,
|
||||
transformSeatsInternalToApi,
|
||||
} from ".";
|
||||
import type { CustomField } from "./booking-fields";
|
||||
|
||||
@@ -621,6 +635,162 @@ describe("transformFutureBookingLimitsInternalToApi", () => {
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform disabled", () => {
|
||||
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
|
||||
periodType: "UNLIMITED",
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformBookerLayoutsInternalToApi", () => {
|
||||
it("should reverse transform booker layout", () => {
|
||||
const transformedField: BookerLayoutsTransformedSchema = {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsOutputEnum_2024_06_14.column_view,
|
||||
BookerLayoutsOutputEnum_2024_06_14.month_view,
|
||||
BookerLayoutsOutputEnum_2024_06_14.week_view,
|
||||
],
|
||||
defaultLayout: BookerLayoutsOutputEnum_2024_06_14.week_view,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.week,
|
||||
};
|
||||
const result = transformBookerLayoutsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformRequiresConfirmationInternalToApi", () => {
|
||||
it("should reverse transform requires confirmation - time", () => {
|
||||
const transformedField = {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
requiresConfirmationWillBlockSlot: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
};
|
||||
const result = transformRequiresConfirmationInternalToApi(
|
||||
transformedField.requiresConfirmation,
|
||||
transformedField.requiresConfirmationWillBlockSlot,
|
||||
transformedField.requiresConfirmationThreshold
|
||||
);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform requires confirmation - always", () => {
|
||||
const transformedField = {
|
||||
requiresConfirmation: true,
|
||||
requiresConfirmationWillBlockSlot: true,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
};
|
||||
const result = transformRequiresConfirmationInternalToApi(
|
||||
transformedField.requiresConfirmation,
|
||||
transformedField.requiresConfirmationWillBlockSlot,
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform requires confirmation - disabled", () => {
|
||||
const transformedField = {
|
||||
requiresConfirmation: false,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
disabled: true,
|
||||
};
|
||||
const result = transformRequiresConfirmationInternalToApi(
|
||||
transformedField.requiresConfirmation,
|
||||
!!undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformEventTypeColorsInternalToApi", () => {
|
||||
it("should reverse transform event type colors", () => {
|
||||
const transformedField: EventTypeColorsTransformedSchema = {
|
||||
darkEventTypeColor: "#292929",
|
||||
lightEventTypeColor: "#fafafa",
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
};
|
||||
|
||||
const result = transformEventTypeColorsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformSeatsInternalToApi", () => {
|
||||
it("should reverse transform event type seats", () => {
|
||||
const transformedSeats: SeatOptionsTransformedSchema = {
|
||||
seatsPerTimeSlot: 10,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
seatsPerTimeSlot: 10,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: false,
|
||||
};
|
||||
|
||||
const result = transformSeatsInternalToApi(transformedSeats);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform event type seats - disabled", () => {
|
||||
const transformedSeats: SeatOptionsDisabledSchema = {
|
||||
seatsPerTimeSlot: null,
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const result = transformSeatsInternalToApi(transformedSeats);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformRecurrenceInternalToApi", () => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ConfirmationPolicyEnum } from "@calcom/platform-enums/monorepo";
|
||||
import type { NoticeThresholdTransformedSchema, ConfirmationPolicy_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export function transformRequiresConfirmationInternalToApi(
|
||||
requiresConfirmation: boolean,
|
||||
requiresConfirmationWillBlockSlot: boolean,
|
||||
requiresConfirmationThreshold?: NoticeThresholdTransformedSchema
|
||||
): ConfirmationPolicy_2024_06_14 | undefined {
|
||||
if (requiresConfirmationThreshold?.unit) {
|
||||
return {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
unit: requiresConfirmationThreshold.unit,
|
||||
count: requiresConfirmationThreshold.time,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: requiresConfirmationWillBlockSlot,
|
||||
};
|
||||
} else if (requiresConfirmation) {
|
||||
return {
|
||||
type: ConfirmationPolicyEnum.ALWAYS,
|
||||
blockUnconfirmedBookingsInBooker: requiresConfirmationWillBlockSlot,
|
||||
};
|
||||
} else if (!requiresConfirmation) {
|
||||
return {
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type {
|
||||
SeatOptionsTransformedSchema,
|
||||
SeatOptionsDisabledSchema,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformSeatsInternalToApi(
|
||||
transformedSeats: SeatOptionsTransformedSchema | SeatOptionsDisabledSchema
|
||||
): CreateEventTypeInput_2024_06_14["seats"] {
|
||||
if (transformedSeats.seatsPerTimeSlot == null) {
|
||||
return {
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
seatsPerTimeSlot: transformedSeats.seatsPerTimeSlot,
|
||||
showAttendeeInfo: transformedSeats.seatsShowAttendees,
|
||||
showAvailabilityCount: transformedSeats.seatsShowAvailabilityCount,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum BookerLayoutsInputEnum_2024_06_14 {
|
||||
month = "month",
|
||||
week = "week",
|
||||
column = "column",
|
||||
}
|
||||
|
||||
export enum BookerLayoutsOutputEnum_2024_06_14 {
|
||||
month_view = "month_view",
|
||||
week_view = "week_view",
|
||||
column_view = "column_view",
|
||||
}
|
||||
@@ -8,4 +8,5 @@ export enum BookingWindowPeriodOutputTypeEnum_2024_06_14 {
|
||||
RANGE = "RANGE",
|
||||
ROLLING_WINDOW = "ROLLING_WINDOW",
|
||||
ROLLING = "ROLLING",
|
||||
UNLIMITED = "UNLIMITED",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum ConfirmationPolicyEnum {
|
||||
ALWAYS = "always",
|
||||
TIME = "time",
|
||||
}
|
||||
|
||||
export enum NoticeThresholdUnitEnum {
|
||||
MINUTES = "minutes",
|
||||
HOURS = "hours",
|
||||
}
|
||||
@@ -5,3 +5,5 @@ export * from "./event-types/interval-limits.enum";
|
||||
export * from "./event-types/period-type";
|
||||
export * from "./event-types/scheduling-type";
|
||||
export * from "./event-types/frequency";
|
||||
export * from "./event-types/booker-layouts.enum";
|
||||
export * from "./event-types/confirmation-policy.enum";
|
||||
|
||||
@@ -5,3 +5,5 @@ export * from "./event-types/interval-limits.enum";
|
||||
export * from "./event-types/period-type";
|
||||
export * from "./event-types/scheduling-type";
|
||||
export * from "./event-types/frequency";
|
||||
export * from "./event-types/booker-layouts.enum";
|
||||
export * from "./event-types/confirmation-policy.enum";
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
## 0.0.38
|
||||
#### Feature: Added Support for AdvancedTab Event-Type Attributes in API
|
||||
- **Booker Layouts**:
|
||||
- Added `transformBookerLayoutsApiToInternal` translator to enable the `bookerLayouts` attribute in the event-type API.
|
||||
- Added `transformBookerLayoutsInternalToApi` translator to improve clarity and readability of `bookerLayouts` response data.
|
||||
|
||||
- **Event-Type Colors**:
|
||||
- Added `transformEventColorsApiToInternal` translator to enable the `color` attribute in the event-type API.
|
||||
- Added `transformEventTypeColorsInternalToApi` translator to enhance the readability of the `color` attribute in the response.
|
||||
|
||||
- **Confirmation Policy**:
|
||||
- Added `transformConfirmationPolicyApiToInternal` translator to enable the `confirmationPolicy` attribute in the event-type API.
|
||||
- Added `transformRequiresConfirmationInternalToApi` translator to improve the readability of `requiresConfirmation` data in the response.
|
||||
|
||||
- **Seats**:
|
||||
- Added `transformSeatsApiToInternal` translator to enable the `seats` attribute in the event-type API.
|
||||
- Added `transformSeatsInternalToApi` translator to enhance readability and clarity of the `seats` data.
|
||||
|
||||
## 0.0.37
|
||||
Released to support PR https://github.com/calcom/cal.com/pull/16200
|
||||
|
||||
|
||||
@@ -100,12 +100,20 @@ export {
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
transformBookerLayoutsApiToInternal,
|
||||
transformConfirmationPolicyApiToInternal,
|
||||
transformEventColorsApiToInternal,
|
||||
transformSeatsApiToInternal,
|
||||
// note(Lauris): Internal to api
|
||||
transformBookingFieldsInternalToApi,
|
||||
transformLocationsInternalToApi,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
transformBookerLayoutsInternalToApi,
|
||||
transformRequiresConfirmationInternalToApi,
|
||||
transformEventTypeColorsInternalToApi,
|
||||
transformSeatsInternalToApi,
|
||||
// note(Lauris): schemas
|
||||
TransformedLocationsSchema,
|
||||
BookingFieldsSchema,
|
||||
@@ -118,7 +126,7 @@ export {
|
||||
|
||||
export type { SystemField, CustomField } from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export { parseBookingLimit } from "@calcom/lib";
|
||||
export { parseBookingLimit, parseEventTypeColor } from "@calcom/lib";
|
||||
|
||||
export { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
export { dynamicEvent } from "@calcom/lib/defaultEvents";
|
||||
@@ -133,3 +141,4 @@ export { getTranslation };
|
||||
export { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
|
||||
export { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
export { validateCustomEventName } from "@calcom/core/event";
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./inputs";
|
||||
export * from "./outputs";
|
||||
export * from "./transformed";
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
|
||||
import { ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
type BookerLayoutsOutputEnum_2024_06_14,
|
||||
} from "@calcom/platform-enums";
|
||||
|
||||
export type BookerLayoutsTransformedSchema = {
|
||||
defaultLayout: BookerLayoutsOutputEnum_2024_06_14;
|
||||
enabledLayouts: BookerLayoutsOutputEnum_2024_06_14[];
|
||||
};
|
||||
|
||||
@ValidatorConstraint({ name: "LayoutValidator", async: false })
|
||||
export class LayoutValidator implements ValidatorConstraintInterface {
|
||||
validate(value: BookerLayoutsInputEnum_2024_06_14 | BookerLayoutsInputEnum_2024_06_14[]) {
|
||||
const validValues = Object.values(BookerLayoutsInputEnum_2024_06_14);
|
||||
|
||||
// If the value is an array, check if every item in the array is valid
|
||||
if (Array.isArray(value)) {
|
||||
return value.every((layout) => validValues.includes(layout));
|
||||
}
|
||||
|
||||
// If the value is a single layout, check if it is valid
|
||||
return validValues.includes(value);
|
||||
}
|
||||
|
||||
defaultMessage() {
|
||||
const validValues = Object.values(BookerLayoutsInputEnum_2024_06_14).join(", ");
|
||||
return `Invalid layout. Valid options are: ${validValues}`;
|
||||
}
|
||||
}
|
||||
|
||||
function IsValidLayout(validationOptions?: ValidationOptions) {
|
||||
return function (object: any, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "IsValidLayout",
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: new LayoutValidator(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export class BookerLayouts_2024_06_14 {
|
||||
@IsValidLayout({ message: "defaultLayout must be one of the valid layouts." })
|
||||
defaultLayout!: BookerLayoutsInputEnum_2024_06_14;
|
||||
|
||||
@IsValidLayout({ message: "enabledLayouts must be one of the valid layouts." })
|
||||
enabledLayouts!: BookerLayoutsInputEnum_2024_06_14[];
|
||||
}
|
||||
+16
-3
@@ -1,6 +1,8 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
|
||||
import { IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
import { IsBoolean, IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
|
||||
import type { Disabled_2024_06_14 } from "./disabled.input";
|
||||
|
||||
export type BookingLimitsKeyOutputType_2024_06_14 = "PER_DAY" | "PER_WEEK" | "PER_MONTH" | "PER_YEAR";
|
||||
export type BookingLimitsKeysInputType = "day" | "week" | "month" | "year";
|
||||
@@ -8,7 +10,7 @@ export type TransformBookingLimitsSchema_2024_06_14 = {
|
||||
[K in BookingLimitsKeyOutputType_2024_06_14]?: number;
|
||||
};
|
||||
|
||||
export class BookingLimitsCount_2024_06_14 {
|
||||
export class BaseBookingLimitsCount_2024_06_14 {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@@ -44,8 +46,13 @@ export class BookingLimitsCount_2024_06_14 {
|
||||
example: 4,
|
||||
})
|
||||
year?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export type BookingLimitsCount_2024_06_14 = BaseBookingLimitsCount_2024_06_14 | Disabled_2024_06_14;
|
||||
// Custom validator to handle the union type
|
||||
@ValidatorConstraint({ name: "BookingLimitsCountValidator", async: false })
|
||||
class BookingLimitsCountValidator implements ValidatorConstraintInterface {
|
||||
@@ -55,6 +62,9 @@ class BookingLimitsCountValidator implements ValidatorConstraintInterface {
|
||||
} = {};
|
||||
validate(value: BookingLimitsCount_2024_06_14) {
|
||||
if (!value) return false;
|
||||
if ("disabled" in value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { day, week, month, year } = value;
|
||||
|
||||
@@ -84,7 +94,10 @@ class BookingLimitsCountValidator implements ValidatorConstraintInterface {
|
||||
|
||||
defaultMessage() {
|
||||
const { invalidLimit, comparedLimit } = this.errorDetails;
|
||||
return `Invalid booking limits: The number of bookings for ${invalidLimit} cannot exceed the number of bookings for ${comparedLimit}.`;
|
||||
if (invalidLimit && comparedLimit) {
|
||||
return `Invalid booking limits: The number of bookings for ${invalidLimit} cannot exceed the number of bookings for ${comparedLimit}.`;
|
||||
}
|
||||
return `Invalid booking limits count structure`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-3
@@ -1,8 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
|
||||
import { IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
import { IsBoolean, IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
|
||||
export class BookingLimitsDuration_2024_06_14 {
|
||||
import type { Disabled_2024_06_14 } from "./disabled.input";
|
||||
|
||||
export class BaseBookingLimitsDuration_2024_06_14 {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(15)
|
||||
@@ -38,8 +40,13 @@ export class BookingLimitsDuration_2024_06_14 {
|
||||
example: 240,
|
||||
})
|
||||
year?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export type BookingLimitsDuration_2024_06_14 = BaseBookingLimitsDuration_2024_06_14 | Disabled_2024_06_14;
|
||||
@ValidatorConstraint({ name: "BookingLimitsDurationValidator", async: false })
|
||||
class BookingLimitsDurationValidator implements ValidatorConstraintInterface {
|
||||
private errorDetails: {
|
||||
@@ -48,6 +55,9 @@ class BookingLimitsDurationValidator implements ValidatorConstraintInterface {
|
||||
} = {};
|
||||
validate(value: BookingLimitsDuration_2024_06_14) {
|
||||
if (!value) return false;
|
||||
if ("disabled" in value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { day, week, month, year } = value;
|
||||
|
||||
@@ -77,7 +87,10 @@ class BookingLimitsDurationValidator implements ValidatorConstraintInterface {
|
||||
|
||||
defaultMessage() {
|
||||
const { invalidLimit, comparedLimit } = this.errorDetails;
|
||||
return `Invalid booking durations: The duration of bookings for ${invalidLimit} cannot exceed the duration of bookings for ${comparedLimit}.`;
|
||||
if (invalidLimit && comparedLimit) {
|
||||
return `Invalid booking durations: The duration of bookings for ${invalidLimit} cannot exceed the duration of bookings for ${comparedLimit}.`;
|
||||
}
|
||||
return `Invalid booking durations structure`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-22
@@ -1,4 +1,3 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
|
||||
import {
|
||||
@@ -16,8 +15,10 @@ import {
|
||||
|
||||
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "@calcom/platform-enums";
|
||||
|
||||
import type { Disabled_2024_06_14 } from "./disabled.input";
|
||||
|
||||
export type BookingWindowPeriodInputType_2024_06_14 = "businessDays" | "calendarDays" | "range";
|
||||
export type BookingWindowPeriodOutputType_2024_06_14 = "RANGE" | "ROLLING_WINDOW" | "ROLLING";
|
||||
export type BookingWindowPeriodOutputType_2024_06_14 = "RANGE" | "ROLLING_WINDOW" | "ROLLING" | "UNLIMITED";
|
||||
|
||||
export type TransformFutureBookingsLimitSchema_2024_06_14 = {
|
||||
periodType: BookingWindowPeriodOutputType_2024_06_14;
|
||||
@@ -52,6 +53,10 @@ export class BusinessDaysWindow_2024_06_14 extends BookingWindowBase {
|
||||
"If true, the window will be rolling aka from the moment that someone is trying to book this event. Otherwise it will be specified amount of days from the current date.",
|
||||
})
|
||||
rolling?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export class CalendarDaysWindow_2024_06_14 extends BookingWindowBase {
|
||||
@@ -68,6 +73,10 @@ export class CalendarDaysWindow_2024_06_14 extends BookingWindowBase {
|
||||
"If true, the window will be rolling aka from the moment that someone is trying to book this event. Otherwise it will be specified amount of days from the current date.",
|
||||
})
|
||||
rolling?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export class RangeWindow_2024_06_14 extends BookingWindowBase {
|
||||
@@ -81,39 +90,48 @@ export class RangeWindow_2024_06_14 extends BookingWindowBase {
|
||||
description: "Date range for when this event can be booked.",
|
||||
})
|
||||
value!: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export type BookingWindow_2024_06_14 =
|
||||
| BusinessDaysWindow_2024_06_14
|
||||
| CalendarDaysWindow_2024_06_14
|
||||
| RangeWindow_2024_06_14;
|
||||
| RangeWindow_2024_06_14
|
||||
| Disabled_2024_06_14;
|
||||
|
||||
// Custom validator to handle the union type
|
||||
@ValidatorConstraint({ name: "bookingWindowValidator", async: false })
|
||||
class BookingWindowValidator implements ValidatorConstraintInterface {
|
||||
validate(value: BookingWindow_2024_06_14) {
|
||||
if (!value || !value.type) {
|
||||
throw new BadRequestException(`'BookingWindow' must have a type`);
|
||||
if ("disabled" in value && value.disabled === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays:
|
||||
return (
|
||||
typeof value.value === "number" &&
|
||||
(typeof (value as BusinessDaysWindow_2024_06_14 | CalendarDaysWindow_2024_06_14).rolling ===
|
||||
"undefined" ||
|
||||
typeof (value as BusinessDaysWindow_2024_06_14 | CalendarDaysWindow_2024_06_14).rolling ===
|
||||
"boolean")
|
||||
);
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.range:
|
||||
return (
|
||||
Array.isArray(value.value) &&
|
||||
value.value.every((date: any) => typeof date === "string" && !isNaN(Date.parse(date)))
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
if ("type" in value) {
|
||||
switch (value.type) {
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays:
|
||||
return (
|
||||
typeof value.value === "number" &&
|
||||
(typeof (value as BusinessDaysWindow_2024_06_14 | CalendarDaysWindow_2024_06_14).rolling ===
|
||||
"undefined" ||
|
||||
typeof (value as BusinessDaysWindow_2024_06_14 | CalendarDaysWindow_2024_06_14).rolling ===
|
||||
"boolean")
|
||||
);
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.range:
|
||||
return (
|
||||
Array.isArray(value.value) &&
|
||||
value.value.every((date: any) => typeof date === "string" && !isNaN(Date.parse(date)))
|
||||
);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultMessage() {
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, IsOptional, IsInt, ValidateNested, IsBoolean } from "class-validator";
|
||||
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
|
||||
import { ValidatorConstraint, registerDecorator } from "class-validator";
|
||||
|
||||
import { ConfirmationPolicyEnum, NoticeThresholdUnitEnum } from "@calcom/platform-enums";
|
||||
|
||||
import type { Disabled_2024_06_14 } from "./disabled.input";
|
||||
|
||||
// Class representing the notice threshold
|
||||
export class NoticeThreshold_2024_06_14 {
|
||||
@IsEnum(NoticeThresholdUnitEnum)
|
||||
@ApiProperty({
|
||||
description: "The unit of time for the notice threshold (e.g., minutes, hours)",
|
||||
example: NoticeThresholdUnitEnum.MINUTES,
|
||||
})
|
||||
unit!: NoticeThresholdUnitEnum;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({
|
||||
description: "The time value for the notice threshold",
|
||||
example: 30,
|
||||
})
|
||||
count!: number;
|
||||
}
|
||||
|
||||
// Class representing the confirmation requirements
|
||||
export class BaseConfirmationPolicy_2024_06_14 {
|
||||
@IsEnum(ConfirmationPolicyEnum)
|
||||
@ApiProperty({
|
||||
description: "The policy that determines when confirmation is required",
|
||||
example: ConfirmationPolicyEnum.ALWAYS,
|
||||
})
|
||||
type!: ConfirmationPolicyEnum;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => NoticeThreshold_2024_06_14)
|
||||
@ApiPropertyOptional({
|
||||
description: "The notice threshold required before confirmation is needed. Required when type is 'time'.",
|
||||
type: NoticeThreshold_2024_06_14,
|
||||
})
|
||||
noticeThreshold?: NoticeThreshold_2024_06_14;
|
||||
|
||||
@IsBoolean()
|
||||
blockUnconfirmedBookingsInBooker!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export type ConfirmationPolicy_2024_06_14 = BaseConfirmationPolicy_2024_06_14 | Disabled_2024_06_14;
|
||||
|
||||
// Validator for confirmation settings
|
||||
@ValidatorConstraint({ name: "ConfirmationPolicyValidator", async: false })
|
||||
export class ConfirmationPolicyValidator implements ValidatorConstraintInterface {
|
||||
validate(value: ConfirmationPolicy_2024_06_14): boolean {
|
||||
if (value.disabled) {
|
||||
return true;
|
||||
}
|
||||
const { type, noticeThreshold } = value;
|
||||
|
||||
if (!type) return false;
|
||||
|
||||
if (type === ConfirmationPolicyEnum.ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === ConfirmationPolicyEnum.TIME) {
|
||||
return !!(
|
||||
noticeThreshold &&
|
||||
typeof noticeThreshold.count === "number" &&
|
||||
typeof noticeThreshold.unit === "string"
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultMessage(): string {
|
||||
return `Invalid requiresConfirmation structure. Use "type": "always" or provide a valid time and unit in "noticeThreshold" for "type": "time".`;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom decorator for confirmation validation
|
||||
export function ValidateConfirmationPolicy(validationOptions?: ValidationOptions) {
|
||||
return function (object: any, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "ValidateConfirmationPolicy",
|
||||
target: object.constructor,
|
||||
propertyName,
|
||||
options: validationOptions,
|
||||
validator: ConfirmationPolicyValidator,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export type NoticeThresholdTransformedSchema = {
|
||||
unit: NoticeThresholdUnitEnum;
|
||||
time: number;
|
||||
};
|
||||
|
||||
export type ConfirmationPolicyTransformedSchema = {
|
||||
requiresConfirmation: boolean;
|
||||
requiresConfirmationThreshold?: NoticeThresholdTransformedSchema;
|
||||
requiresConfirmationWillBlockSlot: boolean;
|
||||
};
|
||||
+82
-12
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty as DocsProperty, getSchemaPath, ApiExtraModels } from "@nestjs/swagger";
|
||||
import { Type, Transform } from "class-transformer";
|
||||
import { Type, Transform, Expose } from "class-transformer";
|
||||
import {
|
||||
IsString,
|
||||
IsInt,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
|
||||
import { SchedulingType } from "@calcom/platform-enums";
|
||||
|
||||
import { BookerLayouts_2024_06_14 } from "./booker-layouts.input";
|
||||
import {
|
||||
AddressFieldInput_2024_06_14,
|
||||
BooleanFieldInput_2024_06_14,
|
||||
@@ -28,11 +29,10 @@ import {
|
||||
} from "./booking-fields.input";
|
||||
import type { InputBookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import { ValidateInputBookingFields_2024_06_14 } from "./booking-fields.input";
|
||||
import { BookingLimitsCount_2024_06_14, ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import {
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
ValidateBookingLimistsDuration,
|
||||
} from "./booking-limits-duration.input";
|
||||
import type { BookingLimitsCount_2024_06_14 } from "./booking-limits-count.input";
|
||||
import { ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import type { BookingLimitsDuration_2024_06_14 } from "./booking-limits-duration.input";
|
||||
import { ValidateBookingLimistsDuration } from "./booking-limits-duration.input";
|
||||
import type { BookingWindow_2024_06_14 } from "./booking-window.input";
|
||||
import {
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
@@ -40,6 +40,11 @@ import {
|
||||
RangeWindow_2024_06_14,
|
||||
ValidateBookingWindow,
|
||||
} from "./booking-window.input";
|
||||
import type { ConfirmationPolicy_2024_06_14 } from "./confirmation-policy.input";
|
||||
import { ValidateConfirmationPolicy } from "./confirmation-policy.input";
|
||||
import { DestinationCalendar_2024_06_14 } from "./destination-calendar.input";
|
||||
import { Disabled_2024_06_14 } from "./disabled.input";
|
||||
import { EventTypeColor_2024_06_14 } from "./event-type-color.input";
|
||||
import {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
@@ -49,6 +54,7 @@ import {
|
||||
} from "./locations.input";
|
||||
import type { Location_2024_06_14 } from "./locations.input";
|
||||
import { Recurrence_2024_06_14 } from "./recurrence.input";
|
||||
import { Seats_2024_06_14 } from "./seats.input";
|
||||
|
||||
export const CREATE_EVENT_LENGTH_EXAMPLE = 60;
|
||||
export const CREATE_EVENT_TITLE_EXAMPLE = "Learn the secrets of masterchief!";
|
||||
@@ -177,7 +183,6 @@ export class CreateEventTypeInput_2024_06_14 {
|
||||
scheduleId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookingLimitsCount_2024_06_14)
|
||||
@ValidateBookingLimistsCount()
|
||||
@DocsProperty({ description: "Limit how many times this event can be booked" })
|
||||
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
|
||||
@@ -191,7 +196,6 @@ export class CreateEventTypeInput_2024_06_14 {
|
||||
onlyShowFirstAvailableSlot?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookingLimitsDuration_2024_06_14)
|
||||
@ValidateBookingLimistsDuration()
|
||||
@DocsProperty({ description: "Limit total amount of time that this event can be booked" })
|
||||
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
|
||||
@@ -217,12 +221,75 @@ export class CreateEventTypeInput_2024_06_14 {
|
||||
offsetStart?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookerLayouts_2024_06_14)
|
||||
bookerLayouts?: BookerLayouts_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateConfirmationPolicy()
|
||||
confirmationPolicy?: ConfirmationPolicy_2024_06_14;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Recurrence_2024_06_14)
|
||||
@DocsProperty({
|
||||
description: "Create a recurring event that can be booked once but will occur multiple times",
|
||||
@Transform(({ value }) => {
|
||||
if (value && typeof value === "object") {
|
||||
if ("interval" in value) {
|
||||
return Object.assign(new Recurrence_2024_06_14(), value);
|
||||
} else if ("disabled" in value) {
|
||||
return Object.assign(new Disabled_2024_06_14(), value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
recurrence?: Recurrence_2024_06_14;
|
||||
@ValidateNested()
|
||||
recurrence?: Recurrence_2024_06_14 | Disabled_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresBookerEmailVerification?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hideCalendarNotes?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
lockTimeZoneToggleOnBookingPage?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => EventTypeColor_2024_06_14)
|
||||
color?: EventTypeColor_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value && typeof value === "object") {
|
||||
if ("seatsPerTimeSlot" in value) {
|
||||
return Object.assign(new Seats_2024_06_14(), value);
|
||||
} else if ("disabled" in value) {
|
||||
return Object.assign(new Disabled_2024_06_14(), value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@ValidateNested()
|
||||
seats?: Seats_2024_06_14 | Disabled_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@DocsProperty({
|
||||
description: `Customizable event name with valid variables:
|
||||
{Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name},
|
||||
{Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION},
|
||||
{HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}`,
|
||||
example: "{Event type title} between {Organiser} and {Scheduler}",
|
||||
})
|
||||
customName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => DestinationCalendar_2024_06_14)
|
||||
destinationCalendar?: DestinationCalendar_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
}
|
||||
|
||||
export enum HostPriority {
|
||||
@@ -233,11 +300,13 @@ export enum HostPriority {
|
||||
highest = "highest",
|
||||
}
|
||||
export class Host {
|
||||
@Expose()
|
||||
@IsInt()
|
||||
@DocsProperty({ description: "Which user is the host of this event" })
|
||||
userId!: number;
|
||||
|
||||
@IsOptional()
|
||||
@Expose()
|
||||
@IsBoolean()
|
||||
@DocsProperty({
|
||||
description:
|
||||
@@ -246,6 +315,7 @@ export class Host {
|
||||
mandatory?: boolean = false;
|
||||
|
||||
@IsEnum(HostPriority)
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@DocsProperty({ enum: HostPriority })
|
||||
priority?: keyof typeof HostPriority = "medium";
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsString } from "class-validator";
|
||||
|
||||
export class DestinationCalendar_2024_06_14 {
|
||||
@ApiProperty({
|
||||
description:
|
||||
"The integration type of the destination calendar. Refer to the /api/v2/calendars endpoint to retrieve the integration type of your connected calendars.",
|
||||
})
|
||||
@IsString()
|
||||
integration!: string;
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
"The external ID of the destination calendar. Refer to the /api/v2/calendars endpoint to retrieve the external IDs of your connected calendars.",
|
||||
})
|
||||
@IsString()
|
||||
externalId!: string;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsOptional } from "class-validator";
|
||||
|
||||
export class Disabled_2024_06_14 {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@ApiProperty({
|
||||
description: "Indicates if the option is disabled",
|
||||
example: true,
|
||||
default: false,
|
||||
})
|
||||
disabled!: true;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsHexColor, IsString } from "class-validator";
|
||||
|
||||
// Class representing the event type colors
|
||||
export class EventTypeColor_2024_06_14 {
|
||||
@IsHexColor()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "Color used for event types in light theme",
|
||||
example: "#292929",
|
||||
})
|
||||
lightThemeHex!: string;
|
||||
|
||||
@IsHexColor()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "Color used for event types in dark theme",
|
||||
example: "#fafafa",
|
||||
})
|
||||
darkThemeHex!: string;
|
||||
}
|
||||
|
||||
export type EventTypeColorsTransformedSchema = {
|
||||
darkEventTypeColor: string;
|
||||
lightEventTypeColor: string;
|
||||
};
|
||||
@@ -7,3 +7,9 @@ export * from "./booking-fields.input";
|
||||
export * from "./recurrence.input";
|
||||
export * from "./update-event-type.input";
|
||||
export * from "./get-event-types-query.input";
|
||||
export * from "./booker-layouts.input";
|
||||
export * from "./confirmation-policy.input";
|
||||
export * from "./event-type-color.input";
|
||||
export * from "./seats.input";
|
||||
export * from "./destination-calendar.input";
|
||||
export * from "./disabled.input";
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsInt, IsEnum } from "class-validator";
|
||||
import { IsInt, IsEnum, IsOptional, IsBoolean } from "class-validator";
|
||||
|
||||
import { FrequencyInput } from "@calcom/platform-enums";
|
||||
|
||||
@@ -21,4 +21,8 @@ export class Recurrence_2024_06_14 {
|
||||
@IsEnum(FrequencyInput)
|
||||
@ApiProperty({ enum: FrequencyInput })
|
||||
frequency!: FrequencyInput;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsInt, IsOptional, Min } from "class-validator";
|
||||
|
||||
// Class representing the seat options
|
||||
export class Seats_2024_06_14 {
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@ApiProperty({
|
||||
description: "Number of seats available per time slot",
|
||||
example: 4,
|
||||
})
|
||||
seatsPerTimeSlot!: number;
|
||||
|
||||
@IsBoolean()
|
||||
@ApiProperty({
|
||||
description: "Show attendee information to other guests",
|
||||
example: true,
|
||||
})
|
||||
showAttendeeInfo!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@ApiProperty({
|
||||
description: "Display the count of available seats",
|
||||
example: true,
|
||||
})
|
||||
showAvailabilityCount!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
disabled?: boolean = false;
|
||||
}
|
||||
|
||||
export type SeatOptionsTransformedSchema = {
|
||||
seatsPerTimeSlot: number;
|
||||
seatsShowAttendees: boolean;
|
||||
seatsShowAvailabilityCount: boolean;
|
||||
};
|
||||
|
||||
export type SeatOptionsDisabledSchema = {
|
||||
seatsPerTimeSlot: null;
|
||||
};
|
||||
+81
-15
@@ -1,7 +1,8 @@
|
||||
import { ApiProperty as DocsProperty, getSchemaPath, ApiExtraModels } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { Type, Transform } from "class-transformer";
|
||||
import { IsString, IsInt, IsBoolean, IsOptional, Min, ValidateNested, IsArray } from "class-validator";
|
||||
|
||||
import { BookerLayouts_2024_06_14 } from "./booker-layouts.input";
|
||||
import type { InputBookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import {
|
||||
AddressFieldInput_2024_06_14,
|
||||
@@ -17,11 +18,10 @@ import {
|
||||
TextFieldInput_2024_06_14,
|
||||
ValidateInputBookingFields_2024_06_14,
|
||||
} from "./booking-fields.input";
|
||||
import { BookingLimitsCount_2024_06_14, ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import {
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
ValidateBookingLimistsDuration,
|
||||
} from "./booking-limits-duration.input";
|
||||
import type { BookingLimitsCount_2024_06_14 } from "./booking-limits-count.input";
|
||||
import { ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import type { BookingLimitsDuration_2024_06_14 } from "./booking-limits-duration.input";
|
||||
import { ValidateBookingLimistsDuration } from "./booking-limits-duration.input";
|
||||
import {
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
ValidateBookingWindow,
|
||||
type BookingWindow_2024_06_14,
|
||||
} from "./booking-window.input";
|
||||
import type { ConfirmationPolicy_2024_06_14 } from "./confirmation-policy.input";
|
||||
import { ValidateConfirmationPolicy } from "./confirmation-policy.input";
|
||||
import {
|
||||
CREATE_EVENT_DESCRIPTION_EXAMPLE,
|
||||
CREATE_EVENT_LENGTH_EXAMPLE,
|
||||
@@ -36,6 +38,9 @@ import {
|
||||
CREATE_EVENT_TITLE_EXAMPLE,
|
||||
Host,
|
||||
} from "./create-event-type.input";
|
||||
import { DestinationCalendar_2024_06_14 } from "./destination-calendar.input";
|
||||
import { Disabled_2024_06_14 } from "./disabled.input";
|
||||
import { EventTypeColor_2024_06_14 } from "./event-type-color.input";
|
||||
import {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
@@ -45,6 +50,7 @@ import {
|
||||
} from "./locations.input";
|
||||
import type { Location_2024_06_14 } from "./locations.input";
|
||||
import { Recurrence_2024_06_14 } from "./recurrence.input";
|
||||
import { Seats_2024_06_14 } from "./seats.input";
|
||||
|
||||
@ApiExtraModels(
|
||||
AddressLocation_2024_06_14,
|
||||
@@ -78,8 +84,8 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
|
||||
title?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@DocsProperty({ example: CREATE_EVENT_SLUG_EXAMPLE })
|
||||
slug?: string;
|
||||
|
||||
@@ -170,7 +176,6 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
scheduleId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookingLimitsCount_2024_06_14)
|
||||
@ValidateBookingLimistsCount()
|
||||
@DocsProperty({ description: "Limit how many times this event can be booked" })
|
||||
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
|
||||
@@ -184,7 +189,6 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
onlyShowFirstAvailableSlot?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookingLimitsDuration_2024_06_14)
|
||||
@ValidateBookingLimistsDuration()
|
||||
@DocsProperty({ description: "Limit total amount of time that this event can be booked" })
|
||||
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
|
||||
@@ -210,12 +214,74 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
offsetStart?: number;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Recurrence_2024_06_14)
|
||||
@DocsProperty({
|
||||
description: "Create a recurring event that can be booked once but will occur multiple times",
|
||||
@Type(() => BookerLayouts_2024_06_14)
|
||||
bookerLayouts?: BookerLayouts_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateConfirmationPolicy()
|
||||
confirmationPolicy?: ConfirmationPolicy_2024_06_14;
|
||||
|
||||
@Transform(({ value }) => {
|
||||
if (value && typeof value === "object") {
|
||||
if ("interval" in value) {
|
||||
return Object.assign(new Recurrence_2024_06_14(), value);
|
||||
} else if ("disabled" in value) {
|
||||
return Object.assign(new Disabled_2024_06_14(), value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
recurrence?: Recurrence_2024_06_14;
|
||||
@ValidateNested()
|
||||
recurrence?: Recurrence_2024_06_14 | Disabled_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresBookerEmailVerification?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hideCalendarNotes?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
lockTimeZoneToggleOnBookingPage?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => EventTypeColor_2024_06_14)
|
||||
color?: EventTypeColor_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value && typeof value === "object") {
|
||||
if ("seatsPerTimeSlot" in value) {
|
||||
return Object.assign(new Seats_2024_06_14(), value);
|
||||
} else if ("disabled" in value) {
|
||||
return Object.assign(new Disabled_2024_06_14(), value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@ValidateNested()
|
||||
seats?: Seats_2024_06_14 | Disabled_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@DocsProperty({
|
||||
description: `Customizable event name with valid variables:
|
||||
{Event type title}, {Organiser}, {Scheduler}, {Location}, {Organiser first name},
|
||||
{Scheduler first name}, {Scheduler last name}, {Event duration}, {LOCATION},
|
||||
{HOST/ATTENDEE}, {HOST}, {ATTENDEE}, {USER}`,
|
||||
example: "{Event type title} between {Organiser} and {Scheduler}",
|
||||
})
|
||||
customName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => DestinationCalendar_2024_06_14)
|
||||
destinationCalendar?: DestinationCalendar_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
}
|
||||
|
||||
@ApiExtraModels(
|
||||
@@ -246,8 +312,8 @@ export class UpdateTeamEventTypeInput_2024_06_14 extends UpdateEventTypeInput_20
|
||||
@DocsProperty()
|
||||
hosts?: Host[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
@DocsProperty({
|
||||
description: "If true, all current and future team members will be assigned to this event type",
|
||||
|
||||
+75
-20
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty as DocsProperty, getSchemaPath, ApiExtraModels } from "@nestjs/swagger";
|
||||
import { ApiProperty as DocsProperty, ApiExtraModels, getSchemaPath } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
@@ -11,16 +11,25 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
import type { Location_2024_06_14, BookingWindow_2024_06_14 } from "../inputs";
|
||||
import {
|
||||
Host as TeamEventTypeHostInput,
|
||||
import type {
|
||||
Location_2024_06_14,
|
||||
BookingWindow_2024_06_14,
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
} from "../inputs";
|
||||
import {
|
||||
EventTypeColor_2024_06_14,
|
||||
Seats_2024_06_14,
|
||||
Host as TeamEventTypeHostInput,
|
||||
BaseBookingLimitsDuration_2024_06_14,
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
RangeWindow_2024_06_14,
|
||||
} from "../inputs";
|
||||
import { Recurrence_2024_06_14 } from "../inputs";
|
||||
import { BookingLimitsCount_2024_06_14 } from "../inputs/booking-limits-count.input";
|
||||
import { BookerLayouts_2024_06_14 } from "../inputs/booker-layouts.input";
|
||||
import type { BookingLimitsCount_2024_06_14 } from "../inputs/booking-limits-count.input";
|
||||
import type { ConfirmationPolicy_2024_06_14 } from "../inputs/confirmation-policy.input";
|
||||
import { DestinationCalendar_2024_06_14 } from "../inputs/destination-calendar.input";
|
||||
import {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
@@ -81,6 +90,7 @@ class User_2024_06_14 {
|
||||
@IsString()
|
||||
darkBrandColor!: string | null;
|
||||
|
||||
@Type(() => Object)
|
||||
metadata!: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -107,7 +117,7 @@ class User_2024_06_14 {
|
||||
SelectFieldOutput_2024_06_14,
|
||||
TextAreaFieldOutput_2024_06_14,
|
||||
TextFieldOutput_2024_06_14,
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
BaseBookingLimitsDuration_2024_06_14,
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
RangeWindow_2024_06_14
|
||||
@@ -132,7 +142,7 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
|
||||
@IsString()
|
||||
@DocsProperty({
|
||||
example: "Discover the culinary wonders of the Argentina by making the best flan ever!",
|
||||
example: "Discover the culinary wonders of Argentina by making the best flan ever!",
|
||||
})
|
||||
description!: string;
|
||||
|
||||
@@ -150,6 +160,7 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
locations!: Location_2024_06_14[];
|
||||
|
||||
@ValidateOutputBookingFields_2024_06_14()
|
||||
@DocsProperty()
|
||||
@DocsProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(NameDefaultFieldOutput_2024_06_14) },
|
||||
@@ -209,10 +220,6 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
@DocsProperty()
|
||||
metadata!: Record<string, unknown>;
|
||||
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
requiresConfirmation!: boolean;
|
||||
|
||||
@IsInt()
|
||||
@DocsProperty()
|
||||
price!: number;
|
||||
@@ -227,7 +234,8 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
|
||||
@IsInt()
|
||||
@DocsProperty()
|
||||
seatsPerTimeSlot!: number | null;
|
||||
@IsOptional()
|
||||
seatsPerTimeSlot?: number | null;
|
||||
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
@@ -239,7 +247,12 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
seatsShowAvailabilityCount!: boolean | null;
|
||||
isInstantEvent!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
seatsShowAvailabilityCount?: boolean | null;
|
||||
|
||||
@IsInt()
|
||||
@DocsProperty()
|
||||
@@ -247,7 +260,6 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
@Type(() => BookingLimitsCount_2024_06_14)
|
||||
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@@ -256,7 +268,6 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
onlyShowFirstAvailableSlot?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookingLimitsDuration_2024_06_14)
|
||||
@DocsProperty()
|
||||
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
|
||||
|
||||
@@ -273,11 +284,55 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
@Type(() => Object)
|
||||
bookingWindow?: BookingWindow_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => BookerLayouts_2024_06_14)
|
||||
@DocsProperty()
|
||||
bookerLayouts?: BookerLayouts_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
confirmationPolicy?: ConfirmationPolicy_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
requiresBookerEmailVerification?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
hideCalendarNotes?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => EventTypeColor_2024_06_14)
|
||||
@DocsProperty()
|
||||
color?: EventTypeColor_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Seats_2024_06_14)
|
||||
@DocsProperty()
|
||||
seats?: Seats_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@DocsProperty()
|
||||
offsetStart?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@DocsProperty()
|
||||
customName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => DestinationCalendar_2024_06_14)
|
||||
@DocsProperty()
|
||||
destinationCalendar?: DestinationCalendar_2024_06_14;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
}
|
||||
|
||||
export class TeamEventTypeResponseHost extends TeamEventTypeHostInput {
|
||||
@@ -298,10 +353,6 @@ export class EventTypeOutput_2024_06_14 extends BaseEventTypeOutput_2024_06_14 {
|
||||
}
|
||||
|
||||
export class TeamEventTypeOutput_2024_06_14 extends BaseEventTypeOutput_2024_06_14 {
|
||||
@IsEnum(SchedulingTypeEnum)
|
||||
@DocsProperty({ enum: SchedulingTypeEnum })
|
||||
schedulingType!: EventTypesOutputSchedulingType | null;
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
@@ -316,7 +367,7 @@ export class TeamEventTypeOutput_2024_06_14 extends BaseEventTypeOutput_2024_06_
|
||||
@IsOptional()
|
||||
@DocsProperty({
|
||||
description:
|
||||
"For managed event types parent event type is the event type that this event type is based on",
|
||||
"For managed event types, parent event type is the event type that this event type is based on",
|
||||
})
|
||||
parentEventTypeId?: number | null;
|
||||
|
||||
@@ -330,4 +381,8 @@ export class TeamEventTypeOutput_2024_06_14 extends BaseEventTypeOutput_2024_06_
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
assignAllTeamMembers?: boolean;
|
||||
|
||||
@IsEnum(SchedulingTypeEnum)
|
||||
@DocsProperty({ enum: SchedulingTypeEnum })
|
||||
schedulingType!: EventTypesOutputSchedulingType | null;
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import type {
|
||||
transformEventColorsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
transformSeatsApiToInternal,
|
||||
transformBookingFieldsApiToInternal,
|
||||
TransformedLocationsSchema,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
|
||||
import type { CreateEventTypeInput_2024_06_14, ConfirmationPolicyTransformedSchema } from "../inputs";
|
||||
|
||||
export type InputEventTransformed_2024_06_14 = Omit<
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
| "lengthInMinutes"
|
||||
| "locations"
|
||||
| "bookingFields"
|
||||
| "bookingLimitsCount"
|
||||
| "bookingLimitsDuration"
|
||||
| "bookingWindow"
|
||||
| "bookerLayouts"
|
||||
| "confirmationPolicy"
|
||||
| "recurrence"
|
||||
| "color"
|
||||
| "seats"
|
||||
| "customName"
|
||||
| "useDestinationCalendarEmail"
|
||||
> & {
|
||||
length: number;
|
||||
slug: string;
|
||||
eventName?: string;
|
||||
bookingLimits?: ReturnType<typeof transformIntervalLimitsApiToInternal>;
|
||||
locations?: z.infer<typeof TransformedLocationsSchema>;
|
||||
bookingFields?: ReturnType<typeof transformBookingFieldsApiToInternal>;
|
||||
durationLimits?: ReturnType<typeof transformIntervalLimitsApiToInternal>;
|
||||
recurringEvent?: ReturnType<typeof transformRecurrenceApiToInternal>;
|
||||
eventTypeColor?: ReturnType<typeof transformEventColorsApiToInternal>;
|
||||
useEventTypeDestinationCalendarEmail?: boolean;
|
||||
} & Partial<
|
||||
Pick<ConfirmationPolicyTransformedSchema, "requiresConfirmation" | "requiresConfirmationWillBlockSlot">
|
||||
> &
|
||||
Partial<ReturnType<typeof transformSeatsApiToInternal>> &
|
||||
Partial<ReturnType<typeof transformFutureBookingLimitsApiToInternal>>;
|
||||
|
||||
export type InputTeamEventTransformed_2024_06_14 = InputEventTransformed_2024_06_14 & {
|
||||
hosts: {
|
||||
userId: number;
|
||||
isFixed: boolean;
|
||||
priority: number;
|
||||
}[];
|
||||
children: {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
eventTypeSlugs: string[];
|
||||
}[];
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./event-type.tranformed";
|
||||
Reference in New Issue
Block a user