From 0dde8c1db9bb7ea41d34b38bee577226986b6d8d Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Wed, 13 Nov 2024 10:13:11 +0100 Subject: [PATCH] feat: v2 multiple length event types and their bookings (#17598) * feat: input lengthInMinutesOptions * Revert "feat: input lengthInMinutesOptions" This reverts commit f23fd8a3861097f0972cc6210b47f25511d85183. * feat: handle lengthInMinutesOptions input * feat: handle lengthInMinutesOptions event output * fix: remove it.only * feat: variable length bookings --- ...ble-length-bookings.controller.e2e-spec.ts | 251 ++++++++++++++++++ .../2024-08-13/services/input.service.ts | 26 +- .../event-types.controller.e2e-spec.ts | 8 + .../services/input-event-types.service.ts | 4 + .../services/output-event-types.service.ts | 1 + .../2024-08-13/inputs/create-booking.input.ts | 10 + .../inputs/create-event-type.input.ts | 15 ++ .../inputs/update-event-type.input.ts | 25 +- .../outputs/event-type.output.ts | 15 ++ 9 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 apps/api/v2/src/ee/bookings/2024-08-13/controllers/variable-length-bookings.controller.e2e-spec.ts diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/variable-length-bookings.controller.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/variable-length-bookings.controller.e2e-spec.ts new file mode 100644 index 0000000000..82fa04ad3f --- /dev/null +++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/variable-length-bookings.controller.e2e-spec.ts @@ -0,0 +1,251 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { CancelBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/cancel-booking.output"; +import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output"; +import { MarkAbsentBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/mark-absent.output"; +import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output"; +import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input"; +import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module"; +import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service"; +import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { UsersModule } from "@/modules/users/users.module"; +import { INestApplication } from "@nestjs/common"; +import { NestExpressApplication } from "@nestjs/platform-express"; +import { Test } from "@nestjs/testing"; +import { EventType, User } from "@prisma/client"; +import * as request from "supertest"; +import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture"; +import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture"; +import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; +import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; +import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; +import { withApiAuth } from "test/utils/withApiAuth"; + +import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants"; +import { CreateBookingInput_2024_08_13, BookingOutput_2024_08_13 } from "@calcom/platform-types"; +import { PlatformOAuthClient, Team } from "@calcom/prisma/client"; + +describe("Bookings Endpoints 2024-08-13", () => { + describe("User bookings", () => { + let app: INestApplication; + let organization: Team; + + let userRepositoryFixture: UserRepositoryFixture; + let bookingsRepositoryFixture: BookingsRepositoryFixture; + let schedulesService: SchedulesService_2024_04_15; + let eventTypesRepositoryFixture: EventTypesRepositoryFixture; + let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let oAuthClient: PlatformOAuthClient; + let teamRepositoryFixture: TeamRepositoryFixture; + + const userEmail = "bookings-controller-e2e@api.com"; + let user: User; + + let variableLengthEventType: EventType; + const variableLengthEventTypeSlug = "variable-length-event"; + let normalEventType: EventType; + + beforeAll(async () => { + const moduleRef = await withApiAuth( + userEmail, + Test.createTestingModule({ + imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15], + }) + ) + .overrideGuard(PermissionsGuard) + .useValue({ + canActivate: () => true, + }) + .compile(); + + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef); + eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef); + oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); + teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); + schedulesService = moduleRef.get(SchedulesService_2024_04_15); + + organization = await teamRepositoryFixture.create({ name: "organization bookings" }); + oAuthClient = await createOAuthClient(organization.id); + + user = await userRepositoryFixture.create({ + email: userEmail, + }); + + const userSchedule: CreateScheduleInput_2024_04_15 = { + name: "working time", + timeZone: "Europe/Rome", + isDefault: true, + }; + await schedulesService.createUserSchedule(user.id, userSchedule); + + variableLengthEventType = await eventTypesRepositoryFixture.create( + { + title: "variable length event", + slug: variableLengthEventTypeSlug, + length: 15, + metadata: { multipleDuration: [15, 30, 60] }, + }, + user.id + ); + + normalEventType = await eventTypesRepositoryFixture.create( + { title: "normal event", slug: "normal-event", length: 15 }, + user.id + ); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + await app.init(); + }); + + async function createOAuthClient(organizationId: number) { + const data = { + logo: "logo-url", + name: "name", + redirectUris: ["http://localhost:5555"], + permissions: 32, + }; + const secret = "secret"; + + const client = await oauthClientRepositoryFixture.create(organizationId, data, secret); + return client; + } + + it("should be defined", () => { + expect(userRepositoryFixture).toBeDefined(); + expect(user).toBeDefined(); + }); + + describe("create bookings", () => { + it("should not be able to specify length of booking for non variable length event type", async () => { + const lengthInMinutes = 30; + const body: CreateBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(), + eventTypeId: normalEventType.id, + lengthInMinutes, + attendee: { + name: "Mr Proper", + email: "mr_proper@gmail.com", + timeZone: "Europe/Rome", + language: "it", + }, + location: "https://meet.google.com/abc-def-ghi", + bookingFieldsResponses: { + customField: "customValue", + }, + }; + + return request(app.getHttpServer()) + .post("/v2/bookings") + .send(body) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(400); + }); + + it("should create a booking with default length", async () => { + const body: CreateBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(), + eventTypeId: variableLengthEventType.id, + attendee: { + name: "Mr Proper", + email: "mr_proper@gmail.com", + timeZone: "Europe/Rome", + language: "it", + }, + location: "https://meet.google.com/abc-def-ghi", + bookingFieldsResponses: { + customField: "customValue", + }, + }; + + return request(app.getHttpServer()) + .post("/v2/bookings") + .send(body) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201) + .then(async (response) => { + const responseBody: CreateBookingOutput_2024_08_13 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseDataIsBooking(responseBody.data)).toBe(true); + + if (responseDataIsBooking(responseBody.data)) { + const data: BookingOutput_2024_08_13 = responseBody.data; + expect(data.id).toBeDefined(); + expect(data.uid).toBeDefined(); + expect(data.status).toEqual("accepted"); + expect(data.start).toEqual(body.start); + expect(data.end).toEqual( + new Date(Date.UTC(2030, 0, 8, 10, variableLengthEventType.length, 0)).toISOString() + ); + expect(data.duration).toEqual(variableLengthEventType.length); + } else { + throw new Error( + "Invalid response data - expected booking but received array of possibily recurring bookings" + ); + } + }); + }); + + it("should create a booking with specified length that is not default length", async () => { + const lengthInMinutes = 30; + const body: CreateBookingInput_2024_08_13 = { + lengthInMinutes, + start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(), + eventTypeId: variableLengthEventType.id, + attendee: { + name: "Mr Proper", + email: "mr_proper@gmail.com", + timeZone: "Europe/Rome", + language: "it", + }, + location: "https://meet.google.com/abc-def-ghi", + bookingFieldsResponses: { + customField: "customValue", + }, + }; + + return request(app.getHttpServer()) + .post("/v2/bookings") + .send(body) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201) + .then(async (response) => { + const responseBody: CreateBookingOutput_2024_08_13 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseDataIsBooking(responseBody.data)).toBe(true); + + if (responseDataIsBooking(responseBody.data)) { + const data: BookingOutput_2024_08_13 = responseBody.data; + expect(data.id).toBeDefined(); + expect(data.uid).toBeDefined(); + expect(data.status).toEqual("accepted"); + expect(data.start).toEqual(body.start); + expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 11, lengthInMinutes, 0)).toISOString()); + expect(data.duration).toEqual(lengthInMinutes); + } else { + throw new Error( + "Invalid response data - expected booking but received array of possibily recurring bookings" + ); + } + }); + }); + }); + + afterAll(async () => { + await oauthClientRepositoryFixture.delete(oAuthClient.id); + await teamRepositoryFixture.delete(organization.id); + await userRepositoryFixture.deleteByEmail(user.email); + await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email); + await app.close(); + }); + }); + + function responseDataIsBooking(data: any): data is BookingOutput_2024_08_13 { + return !Array.isArray(data) && typeof data === "object" && data && "id" in data; + } +}); diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/input.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/input.service.ts index a7953d732e..5a4a5f7e04 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/input.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/input.service.ts @@ -19,6 +19,7 @@ import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; import { X_CAL_CLIENT_ID } from "@calcom/platform-constants"; +import { EventTypeMetaDataSchema } from "@calcom/platform-libraries"; import { CancelBookingInput, CancelBookingInput_2024_08_13, @@ -32,6 +33,7 @@ import { RescheduleBookingInput_2024_08_13, RescheduleSeatedBookingInput_2024_08_13, } from "@calcom/platform-types"; +import { EventType } from "@calcom/prisma/client"; type BookingRequest = NextApiRequest & { userId: number | undefined } & OAuthRequestParams; @@ -123,10 +125,13 @@ export class InputBookingsService_2024_08_13 { throw new NotFoundException(`Event type with id=${inputBooking.eventTypeId} not found`); } + this.validateBookingLengthInMinutes(inputBooking, eventType); + + const lengthInMinutes = inputBooking.lengthInMinutes ?? eventType.length; const startTime = DateTime.fromISO(inputBooking.start, { zone: "utc" }).setZone( inputBooking.attendee.timeZone ); - const endTime = startTime.plus({ minutes: eventType.length }); + const endTime = startTime.plus({ minutes: lengthInMinutes }); return { start: startTime.toISO(), @@ -150,6 +155,25 @@ export class InputBookingsService_2024_08_13 { }; } + validateBookingLengthInMinutes(inputBooking: CreateBookingInput_2024_08_13, eventType: EventType) { + const eventTypeMetadata = EventTypeMetaDataSchema.parse(eventType.metadata); + if (inputBooking.lengthInMinutes && !eventTypeMetadata?.multipleDuration) { + throw new BadRequestException( + "Can't specify 'lengthInMinutes' because event type does not have multiple possible lengths. Please, remove the 'lengthInMinutes' field from the request." + ); + } + if ( + inputBooking.lengthInMinutes && + !eventTypeMetadata?.multipleDuration?.includes(inputBooking.lengthInMinutes) + ) { + throw new BadRequestException( + `Provided 'lengthInMinutes' is not one of the possible lengths for the event type. The possible lengths are: ${eventTypeMetadata?.multipleDuration?.join( + ", " + )}` + ); + } + } + async createRecurringBookingRequest( request: Request, body: CreateRecurringBookingInput_2024_08_13 diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts index ac5b0f1e17..3b22bcfe26 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts @@ -212,6 +212,7 @@ describe("Event types Endpoints", () => { slug: "coding-class", description: "Let's learn how to code like a pro.", lengthInMinutes: 60, + lengthInMinutesOptions: [30, 60, 90], locations: [ { type: "integration", @@ -300,6 +301,7 @@ describe("Event types Endpoints", () => { expect(createdEventType.title).toEqual(body.title); expect(createdEventType.description).toEqual(body.description); expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes); + expect(createdEventType.lengthInMinutesOptions).toEqual(body.lengthInMinutesOptions); expect(createdEventType.locations).toEqual(body.locations); expect(createdEventType.ownerId).toEqual(user.id); expect(createdEventType.scheduleId).toEqual(firstSchedule.id); @@ -360,6 +362,7 @@ describe("Event types Endpoints", () => { expect(fetchedEventType?.title).toEqual(eventType.title); expect(fetchedEventType?.description).toEqual(eventType.description); expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes); + expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions); expect(fetchedEventType?.locations).toEqual(eventType.locations); expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields); expect(fetchedEventType?.ownerId).toEqual(user.id); @@ -665,6 +668,7 @@ describe("Event types Endpoints", () => { const body: UpdateEventTypeInput_2024_06_14 = { title: newTitle, scheduleId: secondSchedule.id, + lengthInMinutesOptions: [15, 30], bookingFields: [ nameBookingField, { @@ -733,6 +737,7 @@ describe("Event types Endpoints", () => { expect(updatedEventType.id).toEqual(eventType.id); expect(updatedEventType.title).toEqual(newTitle); + expect(updatedEventType.lengthInMinutesOptions).toEqual(body.lengthInMinutesOptions); expect(updatedEventType.description).toEqual(eventType.description); expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes); expect(updatedEventType.locations).toEqual(eventType.locations); @@ -772,6 +777,7 @@ describe("Event types Endpoints", () => { eventType.title = newTitle; eventType.scheduleId = secondSchedule.id; + eventType.lengthInMinutesOptions = updatedEventType.lengthInMinutesOptions; eventType.bookingLimitsCount = updatedEventType.bookingLimitsCount; eventType.onlyShowFirstAvailableSlot = updatedEventType.onlyShowFirstAvailableSlot; eventType.bookingLimitsDuration = updatedEventType.bookingLimitsDuration; @@ -819,6 +825,7 @@ describe("Event types Endpoints", () => { expect(fetchedEventType.title).toEqual(eventType.title); expect(fetchedEventType.description).toEqual(eventType.description); expect(fetchedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes); + expect(fetchedEventType.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions); expect(fetchedEventType.locations).toEqual(eventType.locations); expect(fetchedEventType.bookingFields).toEqual(eventType.bookingFields); expect(fetchedEventType.ownerId).toEqual(user.id); @@ -857,6 +864,7 @@ describe("Event types Endpoints", () => { expect(fetchedEventType?.title).toEqual(eventType.title); expect(fetchedEventType?.description).toEqual(eventType.description); expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes); + expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions); expect(fetchedEventType?.locations).toEqual(eventType.locations); expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields); expect(fetchedEventType?.ownerId).toEqual(user.id); diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/input-event-types.service.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/input-event-types.service.ts index 9dbc59ff25..153551ee1c 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/input-event-types.service.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/input-event-types.service.ts @@ -104,6 +104,7 @@ export class InputEventTypesService_2024_06_14 { const { lengthInMinutes, + lengthInMinutesOptions, locations, bookingFields, bookingLimitsCount, @@ -135,6 +136,7 @@ export class InputEventTypesService_2024_06_14 { bookerLayouts: this.transformInputBookerLayouts(bookerLayouts), requiresConfirmationThreshold: confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined, + multipleDuration: lengthInMinutesOptions, }, requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined, requiresConfirmationWillBlockSlot: @@ -152,6 +154,7 @@ export class InputEventTypesService_2024_06_14 { async transformInputUpdateEventType(inputEventType: UpdateEventTypeInput_2024_06_14, eventTypeId: number) { const { lengthInMinutes, + lengthInMinutesOptions, locations, bookingFields, bookingLimitsCount, @@ -191,6 +194,7 @@ export class InputEventTypesService_2024_06_14 { bookerLayouts: this.transformInputBookerLayouts(bookerLayouts), requiresConfirmationThreshold: confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined, + multipleDuration: lengthInMinutesOptions, }, recurringEvent: recurrence ? this.transformInputRecurrignEvent(recurrence) : undefined, requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined, diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts index adb3e8265e..a8f7bae84f 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts @@ -153,6 +153,7 @@ export class OutputEventTypesService_2024_06_14 { id, ownerId, lengthInMinutes: length, + lengthInMinutesOptions: metadata.multipleDuration, title, slug, description: description || "", diff --git a/packages/platform/types/bookings/2024-08-13/inputs/create-booking.input.ts b/packages/platform/types/bookings/2024-08-13/inputs/create-booking.input.ts index 185598d773..b312d3404b 100644 --- a/packages/platform/types/bookings/2024-08-13/inputs/create-booking.input.ts +++ b/packages/platform/types/bookings/2024-08-13/inputs/create-booking.input.ts @@ -64,6 +64,16 @@ export class CreateBookingInput_2024_08_13 { @IsDateString() start!: string; + @IsOptional() + @IsInt() + @Min(1) + @ApiProperty({ + example: 30, + description: `If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here. + If not provided then event type default length will be used for the booking.`, + }) + lengthInMinutes?: number; + @ApiProperty({ type: Number, description: "The ID of the event type that is booked.", diff --git a/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts b/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts index d7fa17cfe5..29c3fc30db 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts @@ -14,6 +14,8 @@ import { IsEnum, IsArray, ValidateNested, + ArrayNotEmpty, + ArrayUnique, } from "class-validator"; import { SchedulingType } from "@calcom/platform-enums"; @@ -108,6 +110,19 @@ export class CreateEventTypeInput_2024_06_14 { @DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE }) lengthInMinutes!: number; + @IsOptional() + @IsArray() + @ArrayNotEmpty() + @ArrayUnique() + @IsInt({ each: true }) + @Min(1, { each: true }) + @DocsProperty({ + example: [15, 30, 60], + description: + "If you want that user can choose between different lengths of the event you can specify them here. Must include the provided `lengthInMinutes`.", + }) + lengthInMinutesOptions?: number[]; + @IsString() @DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE }) title!: string; diff --git a/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts b/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts index bb709a482e..a42a550c8d 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts @@ -5,7 +5,17 @@ import { ApiExtraModels, } from "@nestjs/swagger"; import { Type, Transform } from "class-transformer"; -import { IsString, IsInt, IsBoolean, IsOptional, Min, ValidateNested, IsArray } from "class-validator"; +import { + IsString, + IsInt, + IsBoolean, + IsOptional, + Min, + ValidateNested, + IsArray, + ArrayNotEmpty, + ArrayUnique, +} from "class-validator"; import { BookerLayouts_2024_06_14 } from "./booker-layouts.input"; import type { InputBookingField_2024_06_14 } from "./booking-fields.input"; @@ -99,6 +109,19 @@ export class UpdateEventTypeInput_2024_06_14 { @DocsPropertyOptional({ example: CREATE_EVENT_LENGTH_EXAMPLE }) lengthInMinutes?: number; + @IsOptional() + @IsArray() + @ArrayNotEmpty() + @ArrayUnique() + @IsInt({ each: true }) + @Min(1, { each: true }) + @DocsProperty({ + example: [15, 30, 60], + description: + "If you want that user can choose between different lengths of the event you can specify them here. Must include the provided `lengthInMinutes`.", + }) + lengthInMinutesOptions?: number[]; + @IsOptional() @IsString() @DocsPropertyOptional({ example: CREATE_EVENT_TITLE_EXAMPLE }) diff --git a/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts b/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts index 4b644e0cfa..636ff16747 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts @@ -1,6 +1,8 @@ import { ApiProperty as DocsProperty, ApiExtraModels, getSchemaPath } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { + ArrayNotEmpty, + ArrayUnique, IsArray, IsBoolean, IsEnum, @@ -133,6 +135,19 @@ class BaseEventTypeOutput_2024_06_14 { @DocsProperty({ example: 60 }) lengthInMinutes!: number; + @IsOptional() + @IsArray() + @ArrayNotEmpty() + @ArrayUnique() + @IsInt({ each: true }) + @Min(1, { each: true }) + @DocsProperty({ + example: [15, 30, 60], + description: + "If you want that user can choose between different lengths of the event you can specify them here. Must include the provided `lengthInMinutes`.", + }) + lengthInMinutesOptions?: number[]; + @IsString() @DocsProperty({ example: "Learn the secrets of masterchief!" }) title!: string;