feat: extend api/v2/event-types (#16128)

* feat: extended api/v2/event-types to add more attributes

- additional attributes added
* bookingLimits
* onlyShowFirstAvailableSlot
* durationLimits
* offsetStart
* bookingWindow

* add swagger docs

* type fix

* extend api/v2/teams/[teamId]/event-types

* added tests

* extend api/v2/orgs/[orgId]/teams/[teamid]/event-types

* update: added tests to get and update event-type as well

* rename variables

- bookingLimits -> bookingLimitsCount
- durationLimits -> bookingLimitsDuration

* refactor: rename bookingLimits and durationLimits, add descriptions

- Renamed `bookingLimits` to `bookingLimitsCount`
- Renamed `durationLimits` to `bookingLimitsDuration`
- Added descriptions with examples to `bookingLimitsCount` and `bookingLimitsDuration`

* feat: add unit tests for transformation logic in api-request and api-response

- Added tests for `transformApiEventTypeIntervalLimits`
- Added tests for `transformApiEventTypeFutureBookingLimits`

* fix: type errors

* feat: added custom validators for BookingLimitsCount and BookingLimitsDuration

* fix: unit-tests

* Update CHANGELOG.md

* refactor: update imports to use platform-libraries from @calcom/platform-libraries-1.2.3

* fix: prevent double transformation of bookingWindow and bookingFields

- resolving e2e test failures

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
Somay Chauhan
2024-08-16 09:25:34 +00:00
committed by GitHub
co-authored by Morgan
parent 894c0fc8a7
commit 8fbc76d59a
28 changed files with 1261 additions and 5956 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
},
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.27",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.28",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -26,6 +26,7 @@ import {
EventTypeOutput_2024_06_14,
UpdateEventTypeInput_2024_06_14,
} from "@calcom/platform-types";
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "@calcom/platform-types/dist/event-types/event-types_2024_06_14/inputs/enums/booking-window.enum";
import { SchedulingType } from "@calcom/prisma/enums";
describe("Event types Endpoints", () => {
@@ -214,6 +215,21 @@ describe("Event types Endpoints", () => {
},
],
scheduleId: firstSchedule.id,
bookingLimitsCount: {
day: 2,
week: 5,
},
onlyShowFirstAvailableSlot: true,
bookingLimitsDuration: {
day: 60,
week: 100,
},
offsetStart: 30,
bookingWindow: {
type: BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays,
value: 30,
rolling: true,
},
};
return request(app.getHttpServer())
@@ -232,7 +248,11 @@ describe("Event types Endpoints", () => {
expect(createdEventType.bookingFields).toEqual(body.bookingFields);
expect(createdEventType.ownerId).toEqual(user.id);
expect(createdEventType.scheduleId).toEqual(firstSchedule.id);
expect(createdEventType.bookingLimitsCount).toEqual(body.bookingLimitsCount);
expect(createdEventType.onlyShowFirstAvailableSlot).toEqual(body.onlyShowFirstAvailableSlot);
expect(createdEventType.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
expect(createdEventType.offsetStart).toEqual(body.offsetStart);
expect(createdEventType.bookingWindow).toEqual(body.bookingWindow);
eventType = responseBody.data;
});
});
@@ -243,6 +263,21 @@ describe("Event types Endpoints", () => {
const body: UpdateEventTypeInput_2024_06_14 = {
title: newTitle,
scheduleId: secondSchedule.id,
bookingLimitsCount: {
day: 4,
week: 10,
},
onlyShowFirstAvailableSlot: true,
bookingLimitsDuration: {
day: 100,
week: 200,
},
offsetStart: 50,
bookingWindow: {
type: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
value: 40,
rolling: false,
},
};
return request(app.getHttpServer())
@@ -263,9 +298,19 @@ describe("Event types Endpoints", () => {
expect(updatedEventType.bookingFields).toEqual(eventType.bookingFields);
expect(updatedEventType.ownerId).toEqual(user.id);
expect(updatedEventType.scheduleId).toEqual(secondSchedule.id);
expect(updatedEventType.bookingLimitsCount).toEqual(body.bookingLimitsCount);
expect(updatedEventType.onlyShowFirstAvailableSlot).toEqual(body.onlyShowFirstAvailableSlot);
expect(updatedEventType.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
expect(updatedEventType.offsetStart).toEqual(body.offsetStart);
expect(updatedEventType.bookingWindow).toEqual(body.bookingWindow);
eventType.title = newTitle;
eventType.scheduleId = secondSchedule.id;
eventType.bookingLimitsCount = updatedEventType.bookingLimitsCount;
eventType.onlyShowFirstAvailableSlot = updatedEventType.onlyShowFirstAvailableSlot;
eventType.bookingLimitsDuration = updatedEventType.bookingLimitsDuration;
eventType.offsetStart = updatedEventType.offsetStart;
eventType.bookingWindow = updatedEventType.bookingWindow;
});
});
@@ -301,6 +346,11 @@ describe("Event types Endpoints", () => {
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);
});
it(`/GET/even-types by username`, async () => {
@@ -326,6 +376,11 @@ describe("Event types Endpoints", () => {
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);
});
it(`/GET/event-types by username and eventSlug`, async () => {
@@ -346,6 +401,11 @@ describe("Event types Endpoints", () => {
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);
});
it(`/GET/:id not existing`, async () => {
@@ -13,7 +13,18 @@ import type { PrismaClient } from "@calcom/prisma";
type InputEventTransformed = Omit<
CreateEventTypeInput_2024_06_14,
"lengthInMinutes" | "locations" | "bookingFields"
| "lengthInMinutes"
| "locations"
| "bookingFields"
| "bookingLimitsCount"
| "onlyShowFirstAvailableSlot"
| "bookingLimitsDuration"
| "offsetStart"
| "periodType"
| "periodDays"
| "periodCountCalendarDays"
| "periodStartDate"
| "periodEndDate"
> & {
length: number;
slug: string;
@@ -38,7 +38,16 @@ export class EventTypesService_2024_06_14 {
async createUserEventType(user: UserWithProfile, body: CreateEventTypeInput_2024_06_14) {
await this.checkCanCreateEventType(user.id, body);
const eventTypeUser = await this.getUserToCreateEvent(user);
const bodyTransformed = this.inputEventTypesService.transformInputCreateEventType(body);
const {
bookingLimits,
durationLimits,
periodType = undefined,
periodDays = undefined,
periodCountCalendarDays = undefined,
periodStartDate = undefined,
periodEndDate = undefined,
...bodyTransformed
} = this.inputEventTypesService.transformInputCreateEventType(body);
const { eventType: eventTypeCreated } = await createEventType({
input: bodyTransformed,
ctx: {
@@ -49,6 +58,26 @@ export class EventTypesService_2024_06_14 {
},
});
await updateEventType({
input: {
id: eventTypeCreated.id,
bookingLimits,
durationLimits,
periodType,
periodDays,
periodCountCalendarDays,
periodStartDate,
periodEndDate,
...bodyTransformed,
},
ctx: {
user: eventTypeUser,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prisma: this.dbWrite.prisma,
},
});
const eventType = await this.eventTypesRepository.getEventTypeById(eventTypeCreated.id);
if (!eventType) {
@@ -95,13 +124,16 @@ export class EventTypesService_2024_06_14 {
? await this.membershipsRepository.isUserOrganizationAdmin(user.id, organizationId)
: false;
const profileId = user.movedToProfile?.id || null;
const selectedCalendars = await this.selectedCalendarsRepository.getUserSelectedCalendars(user.id);
return {
id: user.id,
role: user.role,
username: user.username,
organizationId: user.organizationId,
organization: { isOrgAdmin },
profile: { id: profileId },
metadata: user.metadata,
selectedCalendars,
};
}
@@ -3,6 +3,8 @@ import { Injectable } from "@nestjs/common";
import {
transformApiEventTypeBookingFields,
transformApiEventTypeLocations,
transformApiEventTypeIntervalLimits,
transformApiEventTypeFutureBookingLimits,
} from "@calcom/platform-libraries";
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
@@ -16,20 +18,42 @@ export class InputEventTypesService_2024_06_14 {
},
];
const { lengthInMinutes, locations, bookingFields, ...rest } = inputEventType;
const {
lengthInMinutes,
locations,
bookingFields,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
...rest
} = inputEventType;
const eventType = {
...rest,
length: lengthInMinutes,
locations: this.transformInputLocations(locations || defaultLocations),
bookingFields: this.transformInputBookingFields(bookingFields),
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? this.transformInputIntervalLimits(bookingLimitsDuration)
: undefined,
...this.transformInputBookingWindow(bookingWindow),
};
return eventType;
}
transformInputUpdateEventType(inputEventType: UpdateEventTypeInput_2024_06_14) {
const { lengthInMinutes, locations, bookingFields, scheduleId, ...rest } = inputEventType;
const {
lengthInMinutes,
locations,
bookingFields,
scheduleId,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
...rest
} = inputEventType;
const eventType = {
...rest,
@@ -37,6 +61,11 @@ export class InputEventTypesService_2024_06_14 {
locations: locations ? this.transformInputLocations(locations) : undefined,
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
schedule: scheduleId,
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? this.transformInputIntervalLimits(bookingLimitsDuration)
: undefined,
...this.transformInputBookingWindow(bookingWindow),
};
return eventType;
@@ -49,4 +78,13 @@ export class InputEventTypesService_2024_06_14 {
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
return transformApiEventTypeBookingFields(inputBookingFields);
}
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
return transformApiEventTypeIntervalLimits(inputBookingFields);
}
transformInputBookingWindow(inputBookingWindow: CreateEventTypeInput_2024_06_14["bookingWindow"]) {
const res = transformApiEventTypeFutureBookingLimits(inputBookingWindow);
return !!res ? res : {};
}
}
@@ -11,7 +11,11 @@ import {
BookingFieldsSchema,
SystemField,
UserField,
parseBookingLimit,
getResponseEventTypeIntervalLimits,
getResponseEventTypeFutureBookingLimits,
} from "@calcom/platform-libraries";
import { TransformFutureBookingsLimitSchema_2024_06_14 } from "@calcom/platform-types";
type EventTypeRelations = { users: User[]; schedule: Schedule | null };
type DatabaseEventType = EventType & EventTypeRelations;
@@ -44,6 +48,15 @@ type Input = Pick<
| "metadata"
| "users"
| "scheduleId"
| "bookingLimits"
| "durationLimits"
| "onlyShowFirstAvailableSlot"
| "offsetStart"
| "periodType"
| "periodDays"
| "periodCountCalendarDays"
| "periodStartDate"
| "periodEndDate"
>;
@Injectable()
@@ -71,6 +84,8 @@ export class OutputEventTypesService_2024_06_14 {
seatsShowAvailabilityCount,
isInstantEvent,
scheduleId,
onlyShowFirstAvailableSlot,
offsetStart,
} = databaseEventType;
const locations = this.transformLocations(databaseEventType.locations);
@@ -80,6 +95,15 @@ export class OutputEventTypesService_2024_06_14 {
const recurringEvent = this.transformRecurringEvent(databaseEventType.recurringEvent);
const metadata = this.transformMetadata(databaseEventType.metadata) || {};
const users = this.transformUsers(databaseEventType.users);
const bookingLimitsCount = this.transformIntervalLimits(databaseEventType.bookingLimits);
const bookingLimitsDuration = this.transformIntervalLimits(databaseEventType.durationLimits);
const bookingWindow = this.transformBookingWindow({
periodType: databaseEventType.periodType,
periodDays: databaseEventType.periodDays,
periodCountCalendarDays: databaseEventType.periodCountCalendarDays,
periodStartDate: databaseEventType.periodStartDate,
periodEndDate: databaseEventType.periodEndDate,
} as TransformFutureBookingsLimitSchema_2024_06_14);
return {
id,
@@ -109,6 +133,11 @@ export class OutputEventTypesService_2024_06_14 {
isInstantEvent,
users,
scheduleId,
bookingLimitsCount,
bookingLimitsDuration,
onlyShowFirstAvailableSlot,
offsetStart,
bookingWindow,
};
}
@@ -148,4 +177,13 @@ export class OutputEventTypesService_2024_06_14 {
};
});
}
transformIntervalLimits(bookingLimits: any) {
const bookingLimitsParsed = parseBookingLimit(bookingLimits);
return getResponseEventTypeIntervalLimits(bookingLimitsParsed);
}
transformBookingWindow(bookingLimits: TransformFutureBookingsLimitSchema_2024_06_14) {
return getResponseEventTypeFutureBookingLimits(bookingLimits);
}
}
@@ -24,6 +24,7 @@ import {
TeamEventTypeOutput_2024_06_14,
UpdateTeamEventTypeInput_2024_06_14,
} from "@calcom/platform-types";
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/enums/booking-window.enum";
import { Team } from "@calcom/prisma/client";
describe("Organizations Event Types Endpoints", () => {
@@ -260,6 +261,21 @@ describe("Organizations Event Types Endpoints", () => {
userId: teammate2.id,
},
],
bookingLimitsCount: {
day: 2,
week: 5,
},
onlyShowFirstAvailableSlot: true,
bookingLimitsDuration: {
day: 60,
week: 100,
},
offsetStart: 30,
bookingWindow: {
type: BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays,
value: 30,
rolling: true,
},
};
return request(app.getHttpServer())
@@ -276,6 +292,11 @@ describe("Organizations Event Types Endpoints", () => {
expect(data.schedulingType).toEqual("COLLECTIVE");
evaluateHost(body.hosts[0], data.hosts[0]);
evaluateHost(body.hosts[1], data.hosts[1]);
expect(data.bookingLimitsCount).toEqual(body.bookingLimitsCount);
expect(data.onlyShowFirstAvailableSlot).toEqual(body.onlyShowFirstAvailableSlot);
expect(data.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
expect(data.offsetStart).toEqual(body.offsetStart);
expect(data.bookingWindow).toEqual(body.bookingWindow);
collectiveEventType = responseBody.data;
});
@@ -23,7 +23,15 @@ export class InputOrganizationsEventTypesService {
teamId: number,
inputEventType: CreateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, ...rest } = inputEventType;
const {
hosts,
assignAllTeamMembers,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
bookingFields,
...rest
} = inputEventType;
const eventType = this.inputEventTypesService.transformInputCreateEventType(rest);
@@ -35,6 +43,10 @@ export class InputOrganizationsEventTypesService {
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
: this.transformInputHosts(hosts, inputEventType.schedulingType),
assignAllTeamMembers,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
bookingFields,
metadata,
};
@@ -36,11 +36,19 @@ export class OrganizationsEventTypesService {
) {
await this.validateHosts(teamId, body.hosts);
const eventTypeUser = await this.getUserToCreateTeamEvent(user, orgId);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { hosts, assignAllTeamMembers, ...rest } =
await this.inputService.transformInputCreateTeamEventType(teamId, body);
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
hosts,
assignAllTeamMembers,
locations,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
bookingFields,
...rest
} = await this.inputService.transformInputCreateTeamEventType(teamId, body);
const { eventType: eventTypeCreated } = await createEventType({
input: { teamId: teamId, ...rest },
input: { teamId: teamId, locations, ...rest },
ctx: {
user: eventTypeUser,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -52,7 +60,15 @@ export class OrganizationsEventTypesService {
return await this.updateTeamEventType(
eventTypeCreated.id,
teamId,
{ hosts: body.hosts, assignAllTeamMembers },
{
hosts: body.hosts,
assignAllTeamMembers,
bookingLimitsCount,
bookingLimitsDuration,
bookingWindow,
bookingFields,
...rest,
},
user
);
}
@@ -43,6 +43,15 @@ type Input = Pick<
| "userId"
| "parentId"
| "assignAllTeamMembers"
| "bookingLimits"
| "durationLimits"
| "onlyShowFirstAvailableSlot"
| "offsetStart"
| "periodType"
| "periodDays"
| "periodCountCalendarDays"
| "periodStartDate"
| "periodEndDate"
>;
@Injectable()
+74 -34
View File
@@ -6631,33 +6631,6 @@
"type": "string",
"example": "Discover the culinary wonders of the Argentina by making the best flan ever!"
},
"locations": {
"type": "array",
"items": {
"type": "string"
}
},
"bookingFields": {
"type": "array",
"items": {
"type": "string"
}
},
"disableGuests": {
"type": "boolean"
},
"slotInterval": {
"type": "number"
},
"minimumBookingNotice": {
"type": "number"
},
"beforeEventBuffer": {
"type": "number"
},
"afterEventBuffer": {
"type": "number"
},
"schedulingType": {
"type": "object"
},
@@ -6675,13 +6648,6 @@
"lengthInMinutes",
"title",
"description",
"locations",
"bookingFields",
"disableGuests",
"slotInterval",
"minimumBookingNotice",
"beforeEventBuffer",
"afterEventBuffer",
"schedulingType",
"hosts",
"assignAllTeamMembers"
@@ -6779,6 +6745,64 @@
"userId"
]
},
"BookingLimitsCount_2024_06_14": {
"type": "object",
"properties": {
"day": {
"type": "number",
"minimum": 1,
"description": "The number of bookings per day",
"example": 1
},
"week": {
"type": "number",
"minimum": 1,
"description": "The number of bookings per week",
"example": 2
},
"month": {
"type": "number",
"minimum": 1,
"description": "The number of bookings per month",
"example": 3
},
"year": {
"type": "number",
"minimum": 1,
"description": "The number of bookings per year",
"example": 4
}
}
},
"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
}
}
},
"TeamEventTypeOutput_2024_06_14": {
"type": "object",
"properties": {
@@ -6898,6 +6922,22 @@
},
"assignAllTeamMembers": {
"type": "boolean"
},
"bookingLimitsCount": {
"$ref": "#/components/schemas/BookingLimitsCount_2024_06_14"
},
"onlyShowFirstAvailableSlot": {
"type": "boolean"
},
"bookingLimitsDuration": {
"$ref": "#/components/schemas/BookingLimitsDuration_2024_06_14"
},
"bookingWindow": {
"type": "object"
},
"offsetStart": {
"type": "number",
"minimum": 1
}
},
"required": [
@@ -1,12 +1,19 @@
import { describe, expect, it } from "vitest";
import type { BookingField_2024_06_14, Location_2024_06_14 } from "@calcom/platform-types";
import type {
BookingField_2024_06_14,
Location_2024_06_14,
BookingLimitsCount_2024_06_14,
BookingWindow_2024_06_14,
} from "@calcom/platform-types";
import type { UserField, OptionsField } from "./api-request";
import type { UserField } from "./api-request";
import {
transformApiEventTypeLocations,
transformApiEventTypeBookingFields,
transformSelectOptions,
transformApiEventTypeIntervalLimits,
transformApiEventTypeFutureBookingLimits,
} from "./api-request";
describe("transformApiEventTypeLocations", () => {
@@ -336,7 +343,7 @@ describe("transformApiEventTypeBookingFields", () => {
const input: BookingField_2024_06_14[] = [bookingField];
const expectedOutput: OptionsField[] = [
const expectedOutput: UserField[] = [
{
name: bookingField.slug,
type: bookingField.type,
@@ -372,7 +379,7 @@ describe("transformApiEventTypeBookingFields", () => {
const input: BookingField_2024_06_14[] = [bookingField];
const expectedOutput: OptionsField[] = [
const expectedOutput: UserField[] = [
{
name: bookingField.slug,
type: bookingField.type,
@@ -443,7 +450,7 @@ describe("transformApiEventTypeBookingFields", () => {
const input: BookingField_2024_06_14[] = [bookingField];
const expectedOutput: OptionsField[] = [
const expectedOutput: UserField[] = [
{
name: bookingField.slug,
type: bookingField.type,
@@ -479,7 +486,7 @@ describe("transformApiEventTypeBookingFields", () => {
const input: BookingField_2024_06_14[] = [bookingField];
const expectedOutput: OptionsField[] = [
const expectedOutput: UserField[] = [
{
name: bookingField.slug,
type: bookingField.type,
@@ -538,3 +545,111 @@ describe("transformApiEventTypeBookingFields", () => {
expect(result).toEqual(expectedOutput);
});
});
describe("transformApiEventTypeIntervalLimits", () => {
it("should transform booking limits count or booking limits duration", () => {
const input: BookingLimitsCount_2024_06_14 = {
day: 2,
week: 11,
month: 22,
year: 33,
};
const expectedOutput = {
PER_DAY: 2,
PER_WEEK: 11,
PER_MONTH: 22,
PER_YEAR: 33,
};
const result = transformApiEventTypeIntervalLimits(input);
expect(result).toEqual(expectedOutput);
});
});
describe("transformApiEventTypeFutureBookingLimits", () => {
it("should transform range type", () => {
const input: BookingWindow_2024_06_14 = {
type: "range",
value: ["2024-08-06", "2024-08-28"],
};
const expectedOutput = {
periodType: "RANGE",
periodStartDate: new Date("2024-08-06"),
periodEndDate: new Date("2024-08-28"),
};
const result = transformApiEventTypeFutureBookingLimits(input);
expect(result).toEqual(expectedOutput);
});
it("should transform calendar days", () => {
const input: BookingWindow_2024_06_14 = {
type: "calendarDays",
value: 30,
rolling: false,
};
const expectedOutput = {
periodType: "ROLLING",
periodDays: 30,
periodCountCalendarDays: true,
};
const result = transformApiEventTypeFutureBookingLimits(input);
expect(result).toEqual(expectedOutput);
});
it("should transform calendar days rolling-window", () => {
const input: BookingWindow_2024_06_14 = {
type: "calendarDays",
value: 30,
rolling: true,
};
const expectedOutput = {
periodType: "ROLLING_WINDOW",
periodDays: 30,
periodCountCalendarDays: true,
};
const result = transformApiEventTypeFutureBookingLimits(input);
expect(result).toEqual(expectedOutput);
});
it("should transform Business days", () => {
const input: BookingWindow_2024_06_14 = {
type: "businessDays",
value: 30,
rolling: false,
};
const expectedOutput = {
periodType: "ROLLING",
periodDays: 30,
periodCountCalendarDays: false,
};
const result = transformApiEventTypeFutureBookingLimits(input);
expect(result).toEqual(expectedOutput);
});
it("should transform Business days rolling-window", () => {
const input: BookingWindow_2024_06_14 = {
type: "businessDays",
value: 30,
rolling: true,
};
const expectedOutput = {
periodType: "ROLLING_WINDOW",
periodDays: 30,
periodCountCalendarDays: false,
};
const result = transformApiEventTypeFutureBookingLimits(input);
expect(result).toEqual(expectedOutput);
});
});
@@ -1,6 +1,21 @@
import { z } from "zod";
import type { CreateEventTypeInput_2024_06_14, Integration_2024_06_14 } from "@calcom/platform-types";
import type {
BookingLimitsKeyOutputType_2024_06_14,
TransformBookingLimitsSchema_2024_06_14,
} from "@calcom/platform-types";
import {
type CreateEventTypeInput_2024_06_14,
type Integration_2024_06_14,
type BusinessDaysWindow_2024_06_14,
type RangeWindow_2024_06_14,
type TransformFutureBookingsLimitSchema_2024_06_14,
} from "@calcom/platform-types";
import {
BookingWindowPeriodInputTypeEnum_2024_06_14,
BookingWindowPeriodOutputTypeEnum_2024_06_14,
} from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/enums/booking-window.enum";
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/enums/interval-limits.enum";
const integrationsMapping: Record<Integration_2024_06_14, string> = {
"cal-video": "integrations:daily",
@@ -102,6 +117,51 @@ function transformApiEventTypeBookingFields(
return customBookingFields;
}
function transformApiEventTypeIntervalLimits(
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]
) {
const res: TransformBookingLimitsSchema_2024_06_14 = {};
inputBookingLimits &&
Object.entries(inputBookingLimits).map(([key, value]) => {
const outputKey: BookingLimitsKeyOutputType_2024_06_14 = BookingLimitsEnum_2024_06_14[
key as keyof typeof BookingLimitsEnum_2024_06_14
] as BookingLimitsKeyOutputType_2024_06_14;
res[outputKey] = value;
});
return res;
}
function transformApiEventTypeFutureBookingLimits(
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingWindow"]
): TransformFutureBookingsLimitSchema_2024_06_14 | undefined {
switch (inputBookingLimits?.type) {
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
return {
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
periodCountCalendarDays: false,
};
case BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays:
return {
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
periodCountCalendarDays: true,
};
case BookingWindowPeriodInputTypeEnum_2024_06_14.range:
return {
periodType: BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE,
periodStartDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[0]),
periodEndDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[1]),
};
default:
return undefined;
}
}
export function transformSelectOptions(options: string[]) {
return options.map((option) => ({
label: option,
@@ -199,4 +259,9 @@ export type UserField = z.infer<typeof UserFieldsSchema>;
export const BookingFieldsSchema = z.array(z.union([UserFieldsSchema, SystemFieldsSchema]));
export { transformApiEventTypeLocations, transformApiEventTypeBookingFields };
export {
transformApiEventTypeLocations,
transformApiEventTypeBookingFields,
transformApiEventTypeIntervalLimits,
transformApiEventTypeFutureBookingLimits,
};
@@ -5,10 +5,17 @@ import type {
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
IntegrationLocation_2024_06_14,
TransformBookingLimitsSchema_2024_06_14,
TransformFutureBookingsLimitSchema_2024_06_14,
} from "@calcom/platform-types";
import type { UserField, OptionsField } from "./api-request";
import { getResponseEventTypeLocations, getResponseEventTypeBookingFields } from "./api-response";
import type { UserField } from "./api-request";
import {
getResponseEventTypeLocations,
getResponseEventTypeBookingFields,
getResponseEventTypeIntervalLimits,
getResponseEventTypeFutureBookingLimits,
} from "./api-response";
describe("getResponseEventTypeLocations", () => {
it("should reverse transform address location", () => {
@@ -344,7 +351,7 @@ describe("getResponseEventTypeBookingFields", () => {
});
it("should reverse transform select field", () => {
const transformedField: OptionsField[] = [
const transformedField: UserField[] = [
{
name: "your-select",
type: "select",
@@ -384,7 +391,7 @@ describe("getResponseEventTypeBookingFields", () => {
});
it("should reverse transform multiselect field", () => {
const transformedField: OptionsField[] = [
const transformedField: UserField[] = [
{
name: "your-multiselect",
type: "multiselect",
@@ -457,7 +464,7 @@ describe("getResponseEventTypeBookingFields", () => {
});
it("should reverse transform checkbox field", () => {
const transformedField: OptionsField[] = [
const transformedField: UserField[] = [
{
name: "your-checkbox",
type: "checkbox",
@@ -495,7 +502,7 @@ describe("getResponseEventTypeBookingFields", () => {
});
it("should reverse transform radio field", () => {
const transformedField: OptionsField[] = [
const transformedField: UserField[] = [
{
name: "your-radio",
type: "radio",
@@ -566,3 +573,109 @@ describe("getResponseEventTypeBookingFields", () => {
expect(result).toEqual(expectedOutput);
});
});
describe("transformApiEventTypeIntervalLimits", () => {
it("should reverse transform booking limits count or booking limits duration", () => {
const transformedField: TransformBookingLimitsSchema_2024_06_14 = {
PER_DAY: 2,
PER_WEEK: 11,
PER_MONTH: 22,
PER_YEAR: 33,
};
const expectedOutput = {
day: 2,
week: 11,
month: 22,
year: 33,
};
const result = getResponseEventTypeIntervalLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
});
describe("transformApiEventTypeFutureBookingLimits", () => {
it("should reverse transform range type", () => {
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: "RANGE",
periodStartDate: new Date("2024-08-06T09:14:30.000Z"),
periodEndDate: new Date("2024-08-28T18:30:00.000Z"),
};
const expectedOutput = {
type: "range",
value: ["2024-08-06", "2024-08-28"],
};
const result = getResponseEventTypeFutureBookingLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
it("should reverse transform calendar days", () => {
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: "ROLLING",
periodDays: 30,
periodCountCalendarDays: true,
};
const expectedOutput = {
type: "calendarDays",
value: 30,
rolling: false,
};
const result = getResponseEventTypeFutureBookingLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
it("should reverse transform calendar days rolling-window", () => {
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: "ROLLING_WINDOW",
periodDays: 30,
periodCountCalendarDays: true,
};
const expectedOutput = {
type: "calendarDays",
value: 30,
rolling: true,
};
const result = getResponseEventTypeFutureBookingLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
it("should reverse transform Business days", () => {
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: "ROLLING",
periodDays: 30,
periodCountCalendarDays: false,
};
const expectedOutput = {
type: "businessDays",
value: 30,
rolling: false,
};
const result = getResponseEventTypeFutureBookingLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
it("should reverse transform Business days rolling-window", () => {
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: "ROLLING_WINDOW",
periodDays: 30,
periodCountCalendarDays: false,
};
const expectedOutput = {
type: "businessDays",
value: 30,
rolling: true,
};
const result = getResponseEventTypeFutureBookingLimits(transformedField);
expect(result).toEqual(expectedOutput);
});
});
@@ -4,8 +4,20 @@ import type {
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
Integration_2024_06_14,
BookingLimitsKeysInputType,
TransformBookingLimitsSchema_2024_06_14,
TransformFutureBookingsLimitSchema_2024_06_14,
BookingWindow_2024_06_14,
RangeWindow_2024_06_14,
CalendarDaysWindow_2024_06_14,
BusinessDaysWindow_2024_06_14,
BookingField_2024_06_14,
} from "@calcom/platform-types";
import {
BookingWindowPeriodInputTypeEnum_2024_06_14,
BookingWindowPeriodOutputTypeEnum_2024_06_14,
} from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/enums/booking-window.enum";
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/enums/interval-limits.enum";
import type { transformApiEventTypeBookingFields, transformApiEventTypeLocations } from "./api-request";
@@ -189,4 +201,63 @@ function getResponseEventTypeBookingFields(
});
}
export { getResponseEventTypeLocations, getResponseEventTypeBookingFields };
function getResponseEventTypeIntervalLimits(
transformedBookingFields: TransformBookingLimitsSchema_2024_06_14 | null
) {
if (!transformedBookingFields) {
return undefined;
}
const res: { [K in BookingLimitsKeysInputType]?: number } = {};
transformedBookingFields &&
Object.entries(transformedBookingFields).map(([key, value]) => {
const outputKey: BookingLimitsKeysInputType | undefined = Object.keys(
BookingLimitsEnum_2024_06_14
).find(
(item) => BookingLimitsEnum_2024_06_14[item as keyof typeof BookingLimitsEnum_2024_06_14] === key
) as BookingLimitsKeysInputType;
if (outputKey) {
res[outputKey] = value as number;
}
});
return res;
}
function getResponseEventTypeFutureBookingLimits(
transformedFutureBookingsLimitsFields: TransformFutureBookingsLimitSchema_2024_06_14
): BookingWindow_2024_06_14 | undefined {
switch (transformedFutureBookingsLimitsFields?.periodType) {
case BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE:
return {
type: BookingWindowPeriodInputTypeEnum_2024_06_14.range,
value: [
transformedFutureBookingsLimitsFields?.periodStartDate?.toISOString().split("T")[0],
transformedFutureBookingsLimitsFields?.periodEndDate?.toISOString().split("T")[0],
],
} as RangeWindow_2024_06_14;
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW:
return {
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
value: transformedFutureBookingsLimitsFields.periodDays,
rolling: true,
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING:
return {
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
value: transformedFutureBookingsLimitsFields.periodDays,
rolling: false,
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
default:
return undefined;
}
}
export {
getResponseEventTypeLocations,
getResponseEventTypeBookingFields,
getResponseEventTypeIntervalLimits,
getResponseEventTypeFutureBookingLimits,
};
@@ -1,7 +1,7 @@
import type { BookerProps } from "@calcom/features/bookings/Booker";
import { getFieldIdentifier } from "@calcom/features/form-builder/utils/getFieldIdentifier";
import { defaultEvents } from "@calcom/lib/defaultEvents";
import type { CommonField, OptionsField, SystemField } from "@calcom/lib/event-types/transformers";
import type { UserField, SystemField } from "@calcom/lib/event-types/transformers";
import {
transformApiEventTypeLocations,
transformApiEventTypeBookingFields,
@@ -210,7 +210,7 @@ function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
}
function getBookingFields(bookingFields: EventTypeOutput_2024_06_14["bookingFields"]) {
const transformedBookingFields: (CommonField | SystemField | OptionsField)[] =
const transformedBookingFields: (SystemField | UserField)[] =
transformApiEventTypeBookingFields(bookingFields);
// These fields should be added before other user fields
+7 -1
View File
@@ -1,3 +1,10 @@
## 0.0.28
• Feature: Added support for new event-type limits in the API.
• Introduced the transformApiEventTypeFutureBookingLimits translator in packages/lib/event-types/transformers/api-request.ts. This enables the “Limit future bookings” feature in the event-type API.
• Introduced the transformApiEventTypeIntervalLimits translator in packages/lib/event-types/transformers/api-request.ts. This allows the “Limit total booking duration” and “Limit booking frequency” features in the event-type API.
• Added getResponseEventTypeIntervalLimits translator in packages/lib/event-types/transformers/api-response.ts to return data in a more human-friendly format.
• Added getResponseEventTypeFutureBookingLimits translator in packages/lib/event-types/transformers/api-response.ts to improve the clarity and readability of the response data.
## 0.0.26
Update `packages/app-store/office365calendar/lib/CalendarService.ts` "translateEvent" content so that in microsoft outlook calendar event the description
has newlines instead of being all in 1 line.
@@ -6,7 +13,6 @@ has newlines instead of being all in 1 line.
Refactor "packages/lib/event-types/transformers/api-request.ts" getResponseEventTypeBookingFields - make sure that booking fields with options don't have
undefines options.
## 0.0.24
Refactor "packages/lib/event-types/transformers/api-request.ts" - we access event-type booking fields in database and then distinguish them as either
created by the user or system. Then in v2 api "event-types_2024_06_14/services/output-event-types.service.ts" we first parse them and then filter
+5
View File
@@ -93,13 +93,18 @@ export { EventTypeMetaDataSchema, userMetadata } from "@calcom/prisma/zod-utils"
export {
transformApiEventTypeBookingFields,
transformApiEventTypeIntervalLimits,
transformApiEventTypeLocations,
getResponseEventTypeLocations,
getResponseEventTypeBookingFields,
TransformedLocationsSchema,
BookingFieldsSchema,
getResponseEventTypeIntervalLimits,
getResponseEventTypeFutureBookingLimits,
transformApiEventTypeFutureBookingLimits,
} from "@calcom/lib/event-types/transformers";
export { parseBookingLimit } from "@calcom/lib";
export type { SystemField, UserField } from "@calcom/lib/event-types/transformers";
export { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
@@ -0,0 +1,101 @@
import { ApiProperty } from "@nestjs/swagger";
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
import { IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
export type BookingLimitsKeyOutputType_2024_06_14 = "PER_DAY" | "PER_WEEK" | "PER_MONTH" | "PER_YEAR";
export type BookingLimitsKeysInputType = "day" | "week" | "month" | "year";
export type TransformBookingLimitsSchema_2024_06_14 = {
[K in BookingLimitsKeyOutputType_2024_06_14]?: number;
};
export class BookingLimitsCount_2024_06_14 {
@IsOptional()
@IsInt()
@Min(1)
@ApiProperty({
description: "The number of bookings per day",
example: 1,
})
day?: number;
@IsOptional()
@IsInt()
@Min(1)
@ApiProperty({
description: "The number of bookings per week",
example: 2,
})
week?: number;
@IsOptional()
@IsInt()
@Min(1)
@ApiProperty({
description: "The number of bookings per month",
example: 3,
})
month?: number;
@IsOptional()
@IsInt()
@Min(1)
@ApiProperty({
description: "The number of bookings per year",
example: 4,
})
year?: number;
}
// Custom validator to handle the union type
@ValidatorConstraint({ name: "BookingLimitsCountValidator", async: false })
class BookingLimitsCountValidator implements ValidatorConstraintInterface {
private errorDetails: {
invalidLimit?: string;
comparedLimit?: string;
} = {};
validate(value: BookingLimitsCount_2024_06_14) {
if (!value) return false;
const { day, week, month, year } = value;
// Check if 'day' exceeds 'week', 'month', or 'year'
if (day && ((week && day > week) || (month && day > month) || (year && day > year))) {
this.errorDetails.invalidLimit = "day";
this.errorDetails.comparedLimit = week && day > week ? "week" : month && day > month ? "month" : "year";
return false;
}
// Check if 'week' exceeds 'month' or 'year'
if (week && ((month && week > month) || (year && week > year))) {
this.errorDetails.invalidLimit = "week";
this.errorDetails.comparedLimit = month && week > month ? "month" : "year";
return false;
}
// Check if 'month' exceeds 'year'
if (month && year && month > year) {
this.errorDetails.invalidLimit = "month";
this.errorDetails.comparedLimit = "year";
return false;
}
return true;
}
defaultMessage() {
const { invalidLimit, comparedLimit } = this.errorDetails;
return `Invalid booking limits: The number of bookings for ${invalidLimit} cannot exceed the number of bookings for ${comparedLimit}.`;
}
}
export function ValidateBookingLimistsCount(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: "ValidateBookingLimistsCount",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: new BookingLimitsCountValidator(),
});
};
}
@@ -0,0 +1,94 @@
import { ApiProperty } from "@nestjs/swagger";
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
import { IsInt, IsOptional, Min, ValidatorConstraint, registerDecorator } from "class-validator";
export class BookingLimitsDuration_2024_06_14 {
@IsOptional()
@IsInt()
@Min(15)
@ApiProperty({
description: "The duration of bookings per day (must be a multiple of 15)",
example: 60,
})
day?: number;
@IsOptional()
@IsInt()
@Min(15)
@ApiProperty({
description: "The duration of bookings per week (must be a multiple of 15)",
example: 120,
})
week?: number;
@IsOptional()
@IsInt()
@Min(15)
@ApiProperty({
description: "The duration of bookings per month (must be a multiple of 15)",
example: 180,
})
month?: number;
@IsOptional()
@IsInt()
@Min(15)
@ApiProperty({
description: "The duration of bookings per year (must be a multiple of 15)",
example: 240,
})
year?: number;
}
@ValidatorConstraint({ name: "BookingLimitsDurationValidator", async: false })
class BookingLimitsDurationValidator implements ValidatorConstraintInterface {
private errorDetails: {
invalidLimit?: string;
comparedLimit?: string;
} = {};
validate(value: BookingLimitsDuration_2024_06_14) {
if (!value) return false;
const { day, week, month, year } = value;
// Check if 'day' exceeds 'week', 'month', or 'year'
if (day && ((week && day > week) || (month && day > month) || (year && day > year))) {
this.errorDetails.invalidLimit = "day";
this.errorDetails.comparedLimit = week && day > week ? "week" : month && day > month ? "month" : "year";
return false;
}
// Check if 'week' exceeds 'month' or 'year'
if (week && ((month && week > month) || (year && week > year))) {
this.errorDetails.invalidLimit = "week";
this.errorDetails.comparedLimit = month && week > month ? "month" : "year";
return false;
}
// Check if 'month' exceeds 'year'
if (month && year && month > year) {
this.errorDetails.invalidLimit = "month";
this.errorDetails.comparedLimit = "year";
return false;
}
return true;
}
defaultMessage() {
const { invalidLimit, comparedLimit } = this.errorDetails;
return `Invalid booking durations: The duration of bookings for ${invalidLimit} cannot exceed the duration of bookings for ${comparedLimit}.`;
}
}
export function ValidateBookingLimistsDuration(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: "ValidateBookingLimistsDuration",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: new BookingLimitsDurationValidator(),
});
};
}
@@ -0,0 +1,112 @@
import { BadRequestException } from "@nestjs/common";
import type { ValidatorConstraintInterface, ValidationOptions } from "class-validator";
import {
IsEnum,
IsNumber,
IsArray,
ArrayNotEmpty,
IsDateString,
IsDefined,
IsOptional,
ValidatorConstraint,
registerDecorator,
IsBoolean,
} from "class-validator";
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "./enums/booking-window.enum";
export type BookingWindowPeriodInputType_2024_06_14 = "businessDays" | "calendarDays" | "range";
export type BookingWindowPeriodOutputType_2024_06_14 = "RANGE" | "ROLLING_WINDOW" | "ROLLING";
export type TransformFutureBookingsLimitSchema_2024_06_14 = {
periodType: BookingWindowPeriodOutputType_2024_06_14;
periodDays?: number;
periodCountCalendarDays?: boolean;
periodStartDate?: Date;
periodEndDate?: Date;
};
// Base class for common properties and validation
class BookingWindowBase {
@IsEnum(BookingWindowPeriodInputTypeEnum_2024_06_14)
type!: BookingWindowPeriodInputType_2024_06_14;
}
// Separate classes for different value types
export class BusinessDaysWindow_2024_06_14 extends BookingWindowBase {
@IsNumber()
@IsDefined()
value!: number;
@IsOptional()
@IsBoolean()
rolling?: boolean;
}
export class CalendarDaysWindow_2024_06_14 extends BookingWindowBase {
@IsNumber()
@IsDefined()
value!: number;
@IsOptional()
@IsBoolean()
rolling?: boolean;
}
export class RangeWindow_2024_06_14 extends BookingWindowBase {
@IsArray()
@ArrayNotEmpty()
@IsDateString({}, { each: true })
@IsDefined()
value!: string[];
}
export type BookingWindow_2024_06_14 =
| BusinessDaysWindow_2024_06_14
| CalendarDaysWindow_2024_06_14
| RangeWindow_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`);
}
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;
}
}
defaultMessage() {
return "Invalid bookingWindow structure.";
}
}
export function ValidateBookingWindow(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: "ValidateBookingWindow",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: new BookingWindowValidator(),
});
};
}
@@ -13,6 +13,13 @@ import {
import type { BookingField_2024_06_14 } from "./booking-fields.input";
import { ValidateBookingFields_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 { BookingWindow_2024_06_14 } from "./booking-window.input";
import { ValidateBookingWindow } from "./booking-window.input";
import { SchedulingType } from "./enums/scheduling-type";
import { ValidateLocations_2024_06_14 } from "./locations.input";
import type { Location_2024_06_14 } from "./locations.input";
@@ -72,6 +79,29 @@ export class CreateEventTypeInput_2024_06_14 {
@IsInt()
@IsOptional()
scheduleId?: number;
@IsOptional()
@Type(() => BookingLimitsCount_2024_06_14)
@ValidateBookingLimistsCount()
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
@IsOptional()
@IsBoolean()
onlyShowFirstAvailableSlot?: boolean;
@IsOptional()
@Type(() => BookingLimitsDuration_2024_06_14)
@ValidateBookingLimistsDuration()
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
@IsOptional()
@ValidateBookingWindow()
bookingWindow?: BookingWindow_2024_06_14;
@IsOptional()
@IsInt()
@Min(1)
offsetStart?: number;
}
export enum HostPriority {
@@ -94,60 +124,7 @@ export class Host {
priority?: keyof typeof HostPriority = "medium";
}
export class CreateTeamEventTypeInput_2024_06_14 {
@IsInt()
@Min(1)
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
lengthInMinutes!: number;
@IsString()
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
title!: string;
@IsString()
slug!: string;
@IsOptional()
@IsString()
@DocsProperty({ example: CREATE_EVENT_DESCRIPTION_EXAMPLE })
description?: string;
@IsOptional()
@ValidateLocations_2024_06_14()
@DocsProperty()
locations?: Location_2024_06_14[];
@IsOptional()
@ValidateBookingFields_2024_06_14()
@DocsProperty()
bookingFields?: BookingField_2024_06_14[];
@IsBoolean()
@IsOptional()
@DocsProperty()
disableGuests?: boolean;
@IsInt()
@IsOptional()
@DocsProperty()
slotInterval?: number;
@IsInt()
@Min(0)
@IsOptional()
@DocsProperty()
minimumBookingNotice?: number;
@IsInt()
@IsOptional()
@DocsProperty()
beforeEventBuffer?: number;
@IsInt()
@IsOptional()
@DocsProperty()
afterEventBuffer?: number;
export class CreateTeamEventTypeInput_2024_06_14 extends CreateEventTypeInput_2024_06_14 {
@Transform(({ value }) => {
if (value === "collective") {
return SchedulingType.COLLECTIVE;
@@ -0,0 +1,11 @@
export enum BookingWindowPeriodInputTypeEnum_2024_06_14 {
businessDays = "businessDays",
calendarDays = "calendarDays",
range = "range",
}
export enum BookingWindowPeriodOutputTypeEnum_2024_06_14 {
RANGE = "RANGE",
ROLLING_WINDOW = "ROLLING_WINDOW",
ROLLING = "ROLLING",
}
@@ -0,0 +1,6 @@
export enum BookingLimitsEnum_2024_06_14 {
day = "PER_DAY",
week = "PER_WEEK",
month = "PER_MONTH",
year = "PER_YEAR",
}
@@ -1,4 +1,7 @@
export * from "./create-event-type.input";
export * from "./booking-limits-duration.input";
export * from "./booking-limits-count.input";
export * from "./booking-window.input";
export * from "./locations.input";
export * from "./booking-fields.input";
export * from "./recurring-event.input";
@@ -4,6 +4,12 @@ import { IsString, IsInt, IsBoolean, IsOptional, Min, ValidateNested, IsArray }
import type { BookingField_2024_06_14 } from "./booking-fields.input";
import { ValidateBookingFields_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 { ValidateBookingWindow, type BookingWindow_2024_06_14 } from "./booking-window.input";
import { Host } from "./create-event-type.input";
import { ValidateLocations_2024_06_14 } from "./locations.input";
import type { Location_2024_06_14 } from "./locations.input";
@@ -58,6 +64,29 @@ export class UpdateEventTypeInput_2024_06_14 {
@IsInt()
@IsOptional()
scheduleId?: number;
@IsOptional()
@Type(() => BookingLimitsCount_2024_06_14)
@ValidateBookingLimistsCount()
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
@IsOptional()
@IsBoolean()
onlyShowFirstAvailableSlot?: boolean;
@IsOptional()
@Type(() => BookingLimitsDuration_2024_06_14)
@ValidateBookingLimistsDuration()
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
@IsOptional()
@ValidateBookingWindow()
bookingWindow?: BookingWindow_2024_06_14;
@IsOptional()
@IsInt()
@Min(1)
offsetStart?: number;
}
export class UpdateTeamEventTypeInput_2024_06_14 {
@IsOptional()
@@ -128,4 +157,27 @@ export class UpdateTeamEventTypeInput_2024_06_14 {
@IsOptional()
@DocsProperty()
assignAllTeamMembers?: boolean;
@IsOptional()
@Type(() => BookingLimitsCount_2024_06_14)
@ValidateBookingLimistsCount()
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
@IsOptional()
@IsBoolean()
onlyShowFirstAvailableSlot?: boolean;
@IsOptional()
@Type(() => BookingLimitsDuration_2024_06_14)
@ValidateBookingLimistsDuration()
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
@IsOptional()
@ValidateBookingWindow()
bookingWindow?: BookingWindow_2024_06_14;
@IsOptional()
@IsInt()
@Min(1)
offsetStart?: number;
}
@@ -11,10 +11,11 @@ import {
ValidateNested,
} from "class-validator";
import type { Location_2024_06_14, BookingField_2024_06_14 } from "../inputs";
import { Host as TeamEventTypeHostInput } from "../inputs";
import type { Location_2024_06_14, BookingField_2024_06_14, BookingWindow_2024_06_14 } from "../inputs";
import { Host as TeamEventTypeHostInput, BookingLimitsDuration_2024_06_14 } from "../inputs";
import { RecurringEvent_2024_06_14 } from "../inputs";
import { ValidateBookingFields_2024_06_14 } from "../inputs/booking-fields.input";
import type { BookingLimitsCount_2024_06_14 } from "../inputs/booking-limits-count.input";
import { ValidateLocations_2024_06_14 } from "../inputs/locations.input";
enum SchedulingTypeEnum {
@@ -137,6 +138,25 @@ export class EventTypeOutput_2024_06_14 {
@IsInt()
scheduleId!: number | null;
@IsOptional()
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
@IsOptional()
@IsBoolean()
onlyShowFirstAvailableSlot?: boolean;
@IsOptional()
@Type(() => BookingLimitsDuration_2024_06_14)
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
@IsOptional()
bookingWindow?: BookingWindow_2024_06_14;
@IsOptional()
@IsInt()
@Min(1)
offsetStart?: number;
}
export class TeamEventTypeResponseHost extends TeamEventTypeHostInput {
@@ -247,4 +267,23 @@ export class TeamEventTypeOutput_2024_06_14 {
@IsBoolean()
@IsOptional()
assignAllTeamMembers?: boolean;
@IsOptional()
bookingLimitsCount?: BookingLimitsCount_2024_06_14;
@IsOptional()
@IsBoolean()
onlyShowFirstAvailableSlot?: boolean;
@IsOptional()
@Type(() => BookingLimitsDuration_2024_06_14)
bookingLimitsDuration?: BookingLimitsDuration_2024_06_14;
@IsOptional()
bookingWindow?: BookingWindow_2024_06_14;
@IsOptional()
@IsInt()
@Min(1)
offsetStart?: number;
}
+94 -5836
View File
File diff suppressed because it is too large Load Diff