From 8ad80f7ac3da57af2cab0d9277a40cb2eea8489d Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Sat, 13 Apr 2024 11:47:35 +0200 Subject: [PATCH] refactor: v2 event types endpoint paths (#14545) * refactor: rename event-types/:username/public to event-types/public/:username * refactor: move /events/public to /event-types/public/:username/:eventSlug * fix: remove EVENT_TYPE_READ permission from public GET event-types/public/:username * docs: add response DTO to generate docs for /public/:username/:slug aka ex. events/public * tests: public event types endpoints * refactor: have /public at the end of event-types routes * correct swagger doc --- .../event-types.controller.e2e-spec.ts | 41 +- .../controllers/event-types.controller.ts | 41 +- ...et-public-event-type-query-params.input.ts | 11 +- .../outputs/get-event-type-public.output.ts | 355 +++++++++ apps/api/v2/src/modules/endpoints.module.ts | 3 +- .../events/controllers/events.controller.ts | 43 -- .../v2/src/modules/events/events.module.ts | 9 - apps/api/v2/swagger/documentation.json | 704 ++++++++++++++---- .../platform/atoms/hooks/usePublicEvent.tsx | 22 +- packages/platform/types/index.ts | 1 - 10 files changed, 994 insertions(+), 236 deletions(-) rename packages/platform/types/events.ts => apps/api/v2/src/ee/event-types/inputs/get-public-event-type-query-params.input.ts (62%) create mode 100644 apps/api/v2/src/ee/event-types/outputs/get-event-type-public.output.ts delete mode 100644 apps/api/v2/src/modules/events/controllers/events.controller.ts delete mode 100644 apps/api/v2/src/modules/events/events.module.ts diff --git a/apps/api/v2/src/ee/event-types/controllers/event-types.controller.e2e-spec.ts b/apps/api/v2/src/ee/event-types/controllers/event-types.controller.e2e-spec.ts index eb06f44023..77678f8387 100644 --- a/apps/api/v2/src/ee/event-types/controllers/event-types.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/event-types/controllers/event-types.controller.e2e-spec.ts @@ -3,7 +3,9 @@ import { AppModule } from "@/app.module"; import { EventTypesModule } from "@/ee/event-types/event-types.module"; import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input"; import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input"; +import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output"; import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output"; +import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output"; import { HttpExceptionFilter } from "@/filters/http-exception.filter"; import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard"; @@ -64,7 +66,7 @@ describe("Event types Endpoints", () => { let eventTypesRepositoryFixture: EventTypesRepositoryFixture; const userEmail = "event-types-test-e2e@api.com"; - const name = "bob the builder"; + const name = "bob-the-builder"; const username = name; let eventType: EventType; let user: User; @@ -183,6 +185,41 @@ describe("Event types Endpoints", () => { expect(responseBody.data.eventType.userId).toEqual(user.id); }); + it(`/GET/:username/public`, async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/event-types/${username}/public`) + // 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: GetEventTypesPublicOutput = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data?.length).toEqual(1); + expect(responseBody.data?.[0]?.id).toEqual(eventType.id); + expect(responseBody.data?.[0]?.title).toEqual(eventType.title); + expect(responseBody.data?.[0]?.slug).toEqual(eventType.slug); + expect(responseBody.data?.[0]?.length).toEqual(eventType.length); + }); + + it(`/GET/:username/:eventSlug/public`, async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/event-types/${username}/${eventType.slug}/public`) + // 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: GetEventTypePublicOutput = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data?.id).toEqual(eventType.id); + expect(responseBody.data?.title).toEqual(eventType.title); + expect(responseBody.data?.slug).toEqual(eventType.slug); + expect(responseBody.data?.length).toEqual(eventType.length); + }); + it(`/GET/`, async () => { const response = await request(app.getHttpServer()) .get(`/api/v2/event-types`) @@ -202,7 +239,7 @@ describe("Event types Endpoints", () => { expect(responseBody.data.profiles?.[0]?.name).toEqual(name); }); - it(`/GET/:username/public`, async () => { + it(`/GET/public/:username/`, async () => { const response = await request(app.getHttpServer()) .get(`/api/v2/event-types/${username}/public`) // note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above diff --git a/apps/api/v2/src/ee/event-types/controllers/event-types.controller.ts b/apps/api/v2/src/ee/event-types/controllers/event-types.controller.ts index a54436f32d..3c8c7076fb 100644 --- a/apps/api/v2/src/ee/event-types/controllers/event-types.controller.ts +++ b/apps/api/v2/src/ee/event-types/controllers/event-types.controller.ts @@ -1,7 +1,9 @@ import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input"; +import { GetPublicEventTypeQueryParams } from "@/ee/event-types/inputs/get-public-event-type-query-params.input"; import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input"; import { CreateEventTypeOutput } from "@/ee/event-types/outputs/create-event-type.output"; import { DeleteEventTypeOutput } from "@/ee/event-types/outputs/delete-event-type.output"; +import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output"; import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output"; import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output"; import { GetEventTypesOutput } from "@/ee/event-types/outputs/get-event-types.output"; @@ -11,6 +13,7 @@ import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator"; import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard"; import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard"; +import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { UserWithProfile } from "@/modules/users/users.repository"; import { Controller, @@ -24,11 +27,15 @@ import { HttpCode, HttpStatus, Delete, + Query, + InternalServerErrorException, } from "@nestjs/common"; import { ApiTags as DocsTags } from "@nestjs/swagger"; import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants"; +import { getPublicEvent } from "@calcom/platform-libraries"; import { getEventTypesByViewer } from "@calcom/platform-libraries"; +import { PrismaClient } from "@calcom/prisma"; @Controller({ path: "event-types", @@ -37,7 +44,10 @@ import { getEventTypesByViewer } from "@calcom/platform-libraries"; @UseGuards(PermissionsGuard) @DocsTags("Event types") export class EventTypesController { - constructor(private readonly eventTypesService: EventTypesService) {} + constructor( + private readonly eventTypesService: EventTypesService, + private readonly prismaReadService: PrismaReadService + ) {} @Post("/") @Permissions([EVENT_TYPE_WRITE]) @@ -90,6 +100,35 @@ export class EventTypesController { }; } + @Get("/:username/:eventSlug/public") + async getPublicEventType( + @Param("username") username: string, + @Param("eventSlug") eventSlug: string, + @Query() queryParams: GetPublicEventTypeQueryParams + ): Promise { + try { + const event = await getPublicEvent( + username.toLowerCase(), + eventSlug, + queryParams.isTeamEvent, + queryParams.org || null, + this.prismaReadService.prisma as unknown as PrismaClient, + // We should be fine allowing unpublished orgs events to be servable through platform because Platform access is behind license + // If there is ever a need to restrict this, we can introduce a new query param `fromRedirectOfNonOrgLink` + true + ); + return { + data: event, + status: SUCCESS_STATUS, + }; + } catch (err) { + if (err instanceof Error) { + throw new NotFoundException(err.message); + } + } + throw new InternalServerErrorException("Could not find public event."); + } + @Get("/:username/public") async getPublicEventTypes(@Param("username") username: string): Promise { const eventTypes = await this.eventTypesService.getEventTypesPublicByUsername(username); diff --git a/packages/platform/types/events.ts b/apps/api/v2/src/ee/event-types/inputs/get-public-event-type-query-params.input.ts similarity index 62% rename from packages/platform/types/events.ts rename to apps/api/v2/src/ee/event-types/inputs/get-public-event-type-query-params.input.ts index 084d13639c..a94dc9ea26 100644 --- a/packages/platform/types/events.ts +++ b/apps/api/v2/src/ee/event-types/inputs/get-public-event-type-query-params.input.ts @@ -2,16 +2,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { Transform } from "class-transformer"; import { IsBoolean, IsOptional, IsString } from "class-validator"; -export class GetPublicEventInput { - @IsString() - @Transform(({ value }: { value: string }) => value.toLowerCase()) - @ApiProperty({ required: true }) - username!: string; - - @IsString() - @ApiProperty({ required: true }) - eventSlug!: string; - +export class GetPublicEventTypeQueryParams { @Transform(({ value }: { value: string }) => value === "true") @IsBoolean() @IsOptional() diff --git a/apps/api/v2/src/ee/event-types/outputs/get-event-type-public.output.ts b/apps/api/v2/src/ee/event-types/outputs/get-event-type-public.output.ts new file mode 100644 index 0000000000..887e1f6bef --- /dev/null +++ b/apps/api/v2/src/ee/event-types/outputs/get-event-type-public.output.ts @@ -0,0 +1,355 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { + IsBoolean, + IsInt, + IsOptional, + IsString, + IsUrl, + ValidateNested, + IsArray, + IsObject, + IsNumber, + IsEnum, +} from "class-validator"; + +import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; + +class Location { + @IsString() + type!: string; +} + +class Source { + @IsString() + id!: string; + + @IsString() + type!: string; + + @IsString() + label!: string; +} + +class OptionInput { + @IsString() + type!: string; + + @IsBoolean() + @IsOptional() + required?: boolean; + + @IsString() + @IsOptional() + placeholder?: string; +} + +class BookingField { + @IsString() + name!: string; + + @IsString() + type!: string; + + @IsOptional() + @IsString() + defaultLabel?: string; + + @IsString() + @IsOptional() + label?: string; + + @IsString() + @IsOptional() + placeholder?: string; + + @IsBoolean() + @IsOptional() + required?: boolean; + + @IsOptional() + getOptionsAt?: string; + + @IsObject() + @IsOptional() + optionsInputs?: { [key: string]: OptionInput }; + + @IsBoolean() + @IsOptional() + hideWhenJustOneOption?: boolean; + + @IsString() + @IsOptional() + editable?: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => Source) + @IsOptional() + sources?: Source[]; +} + +class Organization { + @IsInt() + id!: number; + + @IsString() + @IsOptional() + slug?: string | null; + + @IsString() + name!: string; + + @IsOptional() + metadata!: Record; +} + +class Profile { + @IsString() + username!: string | null; + + @IsInt() + id!: number | null; + + @IsInt() + @IsOptional() + userId?: number; + + @IsString() + @IsOptional() + uid?: string; + + @IsOptional() + @IsString() + name?: string; + + @IsInt() + organizationId!: number | null; + + @ValidateNested() + @Type(() => Organization) + organization?: Organization | null; + + @IsString() + upId!: string; + + @IsString() + @IsOptional() + image?: string; + + @IsString() + @IsOptional() + brandColor?: string; + + @IsString() + @IsOptional() + darkBrandColor?: string; + + @IsString() + @IsOptional() + theme?: string; + + @IsOptional() + bookerLayouts?: any; +} + +class Owner { + @IsInt() + id!: number; + + @IsString() + @IsOptional() + avatarUrl?: string | null; + + @IsString() + username!: string | null; + + @IsString() + name!: string | null; + + @IsString() + weekStart!: string; + + @IsString() + @IsOptional() + brandColor?: string | null; + + @IsString() + @IsOptional() + darkBrandColor?: string | null; + + @IsString() + @IsOptional() + theme?: string | null; + + @IsOptional() + metadata!: any; + + @IsInt() + @IsOptional() + defaultScheduleId?: number | null; + + @IsString() + nonProfileUsername!: string | null; + + @ValidateNested() + @Type(() => Profile) + profile!: Profile; +} + +class User { + @IsString() + username!: string | null; + + @IsString() + name!: string | null; + + @IsString() + weekStart!: string; + + @IsInt() + organizationId?: number; + + @IsString() + @IsOptional() + avatarUrl?: string | null; + + @ValidateNested() + profile!: Profile; + + @IsString() + bookerUrl!: string; +} + +class Schedule { + @IsInt() + id!: number; + + @IsString() + timeZone!: string | null; +} + +class PublicEventTypeOutput { + @IsInt() + id!: number; + + @IsString() + title!: string; + + @IsString() + description!: string; + + @IsString() + @IsOptional() + eventName?: string | null; + + @IsString() + slug!: string; + + @IsBoolean() + isInstantEvent!: boolean; + + @IsOptional() + aiPhoneCallConfig?: any; + + @IsOptional() + schedulingType?: any; + + @IsInt() + length!: number; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => Location) + locations!: Location[]; + + @IsArray() + customInputs!: any[]; + + @IsBoolean() + disableGuests!: boolean; + + @IsObject() + metadata!: object | null; + + @IsBoolean() + lockTimeZoneToggleOnBookingPage!: boolean; + + @IsBoolean() + requiresConfirmation!: boolean; + + @IsBoolean() + requiresBookerEmailVerification!: boolean; + + @IsOptional() + recurringEvent?: any; + + @IsNumber() + price!: number; + + @IsString() + currency!: string; + + @IsOptional() + seatsPerTimeSlot?: number | null; + + @IsBoolean() + seatsShowAvailabilityCount!: boolean | null; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => BookingField) + bookingFields!: BookingField[]; + + @IsOptional() + team?: any; + + @IsOptional() + @IsUrl() + successRedirectUrl?: string | null; + + @IsArray() + workflows!: any[]; + + @IsArray() + hosts!: any[]; + + @ValidateNested() + @Type(() => Owner) + owner!: Owner | null; + + @ValidateNested() + @Type(() => Schedule) + schedule!: Schedule | null; + + @IsBoolean() + hidden!: boolean; + + @IsBoolean() + assignAllTeamMembers!: boolean; + + @IsOptional() + bookerLayouts?: any; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => User) + users!: User[]; + + @IsObject() + entity!: object; + + @IsBoolean() + isDynamic!: boolean; +} + +export class GetEventTypePublicOutput { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @ValidateNested({ each: true }) + @Type(() => PublicEventTypeOutput) + @IsArray() + data!: PublicEventTypeOutput | null; +} diff --git a/apps/api/v2/src/modules/endpoints.module.ts b/apps/api/v2/src/modules/endpoints.module.ts index f968aed467..de9be4b261 100644 --- a/apps/api/v2/src/modules/endpoints.module.ts +++ b/apps/api/v2/src/modules/endpoints.module.ts @@ -1,12 +1,11 @@ import { PlatformEndpointsModule } from "@/ee/platform-endpoints-module"; -import { EventsModule } from "@/modules/events/events.module"; import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module"; import { TimezoneModule } from "@/modules/timezones/timezones.module"; import type { MiddlewareConsumer, NestModule } from "@nestjs/common"; import { Module } from "@nestjs/common"; @Module({ - imports: [EventsModule, OAuthClientModule, PlatformEndpointsModule, TimezoneModule], + imports: [OAuthClientModule, PlatformEndpointsModule, TimezoneModule], }) export class EndpointsModule implements NestModule { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/apps/api/v2/src/modules/events/controllers/events.controller.ts b/apps/api/v2/src/modules/events/controllers/events.controller.ts deleted file mode 100644 index 6dd7cc9fca..0000000000 --- a/apps/api/v2/src/modules/events/controllers/events.controller.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; -import { Controller, Get, NotFoundException, InternalServerErrorException, Query } from "@nestjs/common"; -import { ApiTags as DocsTags } from "@nestjs/swagger"; - -import { SUCCESS_STATUS } from "@calcom/platform-constants"; -import { getPublicEvent } from "@calcom/platform-libraries"; -import type { PublicEventType } from "@calcom/platform-libraries"; -import { ApiResponse, GetPublicEventInput } from "@calcom/platform-types"; -import { PrismaClient } from "@calcom/prisma"; - -@Controller({ - path: "events", - version: "2", -}) -@DocsTags("Event types") -export class EventsController { - constructor(private readonly prismaReadService: PrismaReadService) {} - - @Get("/public") - async getPublicEvent(@Query() queryParams: GetPublicEventInput): Promise> { - try { - const event = await getPublicEvent( - queryParams.username, - queryParams.eventSlug, - queryParams.isTeamEvent, - queryParams.org || null, - this.prismaReadService.prisma as unknown as PrismaClient, - // We should be fine allowing unpublished orgs events to be servable through platform because Platform access is behind license - // If there is ever a need to restrict this, we can introduce a new query param `fromRedirectOfNonOrgLink` - true - ); - return { - data: event, - status: SUCCESS_STATUS, - }; - } catch (err) { - if (err instanceof Error) { - throw new NotFoundException(err.message); - } - } - throw new InternalServerErrorException("Could not find public event."); - } -} diff --git a/apps/api/v2/src/modules/events/events.module.ts b/apps/api/v2/src/modules/events/events.module.ts deleted file mode 100644 index 57c8053271..0000000000 --- a/apps/api/v2/src/modules/events/events.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EventsController } from "@/modules/events/controllers/events.controller"; -import { PrismaModule } from "@/modules/prisma/prisma.module"; -import { Module } from "@nestjs/common"; - -@Module({ - imports: [PrismaModule], - controllers: [EventsController], -}) -export class EventsModule {} diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 8d918575ac..62e5924af7 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -22,61 +22,7 @@ ] } }, - "/v2/events/public": { - "get": { - "operationId": "EventsController_getPublicEvent", - "parameters": [ - { - "name": "username", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "eventSlug", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "isTeamEvent", - "required": false, - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "org", - "required": false, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - } - }, - "tags": [ - "Event types" - ] - } - }, - "/v2/oauth-clients/{clientId}/users": { + "/api/v2/oauth-clients/{clientId}/users": { "get": { "operationId": "OAuthClientUsersController_getManagedUsers", "parameters": [ @@ -144,7 +90,7 @@ ] } }, - "/v2/oauth-clients/{clientId}/users/{userId}": { + "/api/v2/oauth-clients/{clientId}/users/{userId}": { "get": { "operationId": "OAuthClientUsersController_getUserById", "parameters": [ @@ -264,7 +210,7 @@ ] } }, - "/v2/oauth-clients/{clientId}/users/{userId}/force-refresh": { + "/api/v2/oauth-clients/{clientId}/users/{userId}/force-refresh": { "post": { "operationId": "OAuthClientUsersController_forceRefresh", "parameters": [ @@ -302,7 +248,7 @@ ] } }, - "/v2/oauth-clients": { + "/api/v2/oauth-clients": { "post": { "operationId": "OAuthClientsController_createOAuthClient", "summary": "", @@ -356,7 +302,7 @@ ] } }, - "/v2/oauth-clients/{clientId}": { + "/api/v2/oauth-clients/{clientId}": { "get": { "operationId": "OAuthClientsController_getOAuthClientById", "summary": "", @@ -458,7 +404,7 @@ ] } }, - "/v2/oauth/{clientId}/authorize": { + "/api/v2/oauth/{clientId}/authorize": { "post": { "operationId": "OAuthFlowController_authorize", "summary": "Authorize an OAuth client", @@ -496,7 +442,7 @@ ] } }, - "/v2/oauth/{clientId}/exchange": { + "/api/v2/oauth/{clientId}/exchange": { "post": { "operationId": "OAuthFlowController_exchange", "summary": "Exchange authorization code for access tokens", @@ -549,7 +495,7 @@ ] } }, - "/v2/oauth/{clientId}/refresh": { + "/api/v2/oauth/{clientId}/refresh": { "post": { "operationId": "OAuthFlowController_refreshAccessToken", "parameters": [ @@ -597,7 +543,7 @@ ] } }, - "/v2/event-types": { + "/api/v2/event-types": { "post": { "operationId": "EventTypesController_createEventType", "parameters": [], @@ -647,7 +593,7 @@ ] } }, - "/v2/event-types/{eventTypeId}": { + "/api/v2/event-types/{eventTypeId}": { "get": { "operationId": "EventTypesController_getEventType", "parameters": [ @@ -743,7 +689,61 @@ ] } }, - "/v2/event-types/{username}/public": { + "/api/v2/event-types/{username}/{eventSlug}/public": { + "get": { + "operationId": "EventTypesController_getPublicEventType", + "parameters": [ + { + "name": "username", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "eventSlug", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "isTeamEvent", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "org", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEventTypePublicOutput" + } + } + } + } + }, + "tags": [ + "Event types" + ] + } + }, + "/api/v2/event-types/{username}/public": { "get": { "operationId": "EventTypesController_getPublicEventTypes", "parameters": [ @@ -773,7 +773,7 @@ ] } }, - "/v2/ee/gcal/oauth/auth-url": { + "/api/v2/ee/gcal/oauth/auth-url": { "get": { "operationId": "GcalController_redirect", "parameters": [ @@ -803,7 +803,7 @@ ] } }, - "/v2/ee/gcal/oauth/save": { + "/api/v2/ee/gcal/oauth/save": { "get": { "operationId": "GcalController_save", "parameters": [ @@ -841,7 +841,7 @@ ] } }, - "/v2/ee/gcal/check": { + "/api/v2/ee/gcal/check": { "get": { "operationId": "GcalController_check", "parameters": [], @@ -862,7 +862,7 @@ ] } }, - "/v2/ee/provider/{clientId}": { + "/api/v2/ee/provider/{clientId}": { "get": { "operationId": "CalProviderController_verifyClientId", "parameters": [ @@ -892,7 +892,7 @@ ] } }, - "/v2/ee/provider/{clientId}/access-token": { + "/api/v2/ee/provider/{clientId}/access-token": { "get": { "operationId": "CalProviderController_verifyAccessToken", "parameters": [ @@ -922,7 +922,7 @@ ] } }, - "/v2/schedules": { + "/api/v2/schedules": { "post": { "operationId": "SchedulesController_createSchedule", "parameters": [], @@ -972,7 +972,7 @@ ] } }, - "/v2/schedules/default": { + "/api/v2/schedules/default": { "get": { "operationId": "SchedulesController_getDefaultSchedule", "parameters": [], @@ -993,7 +993,7 @@ ] } }, - "/v2/schedules/{scheduleId}": { + "/api/v2/schedules/{scheduleId}": { "get": { "operationId": "SchedulesController_getSchedule", "parameters": [ @@ -1089,7 +1089,7 @@ ] } }, - "/v2/ee/me": { + "/api/v2/ee/me": { "get": { "operationId": "MeController_getMe", "parameters": [], @@ -1139,7 +1139,7 @@ ] } }, - "/v2/ee/calendars/busy-times": { + "/api/v2/ee/calendars/busy-times": { "get": { "operationId": "CalendarsController_getBusyTimes", "parameters": [], @@ -1160,7 +1160,7 @@ ] } }, - "/v2/ee/calendars": { + "/api/v2/ee/calendars": { "get": { "operationId": "CalendarsController_getCalendars", "parameters": [], @@ -1181,7 +1181,7 @@ ] } }, - "/v2/ee/bookings": { + "/api/v2/ee/bookings": { "get": { "operationId": "BookingsController_getBookings", "parameters": [ @@ -1235,16 +1235,7 @@ }, "post": { "operationId": "BookingsController_createBooking", - "parameters": [ - { - "name": "x-cal-client-id", - "required": true, - "in": "header", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "requestBody": { "required": true, "content": { @@ -1272,7 +1263,7 @@ ] } }, - "/v2/ee/bookings/{bookingUid}": { + "/api/v2/ee/bookings/{bookingUid}": { "get": { "operationId": "BookingsController_getBooking", "parameters": [ @@ -1302,7 +1293,7 @@ ] } }, - "/v2/ee/bookings/{bookingUid}/reschedule": { + "/api/v2/ee/bookings/{bookingUid}/reschedule": { "get": { "operationId": "BookingsController_getBookingForReschedule", "parameters": [ @@ -1332,7 +1323,7 @@ ] } }, - "/v2/ee/bookings/{bookingId}/cancel": { + "/api/v2/ee/bookings/{bookingId}/cancel": { "post": { "operationId": "BookingsController_cancelBooking", "parameters": [ @@ -1343,14 +1334,6 @@ "schema": { "type": "string" } - }, - { - "name": "x-cal-client-id", - "required": true, - "in": "header", - "schema": { - "type": "string" - } } ], "requestBody": { @@ -1380,19 +1363,10 @@ ] } }, - "/v2/ee/bookings/reccuring": { + "/api/v2/ee/bookings/reccuring": { "post": { "operationId": "BookingsController_createReccuringBooking", - "parameters": [ - { - "name": "x-cal-client-id", - "required": true, - "in": "header", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "requestBody": { "required": true, "content": { @@ -1423,19 +1397,10 @@ ] } }, - "/v2/ee/bookings/instant": { + "/api/v2/ee/bookings/instant": { "post": { "operationId": "BookingsController_createInstantBooking", - "parameters": [ - { - "name": "x-cal-client-id", - "required": true, - "in": "header", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "requestBody": { "required": true, "content": { @@ -1463,7 +1428,7 @@ ] } }, - "/v2/slots/reserve": { + "/api/v2/slots/reserve": { "post": { "operationId": "SlotsController_reserveSlot", "parameters": [], @@ -1494,7 +1459,7 @@ ] } }, - "/v2/slots/selected-slot": { + "/api/v2/slots/selected-slot": { "delete": { "operationId": "SlotsController_deleteSelectedSlot", "parameters": [], @@ -1515,7 +1480,7 @@ ] } }, - "/v2/slots/available": { + "/api/v2/slots/available": { "get": { "operationId": "SlotsController_getAvailableSlots", "parameters": [], @@ -1536,7 +1501,7 @@ ] } }, - "/v2/timezones": { + "/api/v2/timezones": { "get": { "operationId": "TimezonesController_getTimeZones", "parameters": [], @@ -2184,6 +2149,462 @@ "data" ] }, + "Location": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Source": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "id", + "type", + "label" + ] + }, + "BookingField": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "defaultLabel": { + "type": "string" + }, + "label": { + "type": "string" + }, + "placeholder": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "getOptionsAt": { + "type": "string" + }, + "hideWhenJustOneOption": { + "type": "boolean" + }, + "editable": { + "type": "string" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Source" + } + } + }, + "required": [ + "name", + "type" + ] + }, + "Organization": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "slug": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string" + }, + "metadata": { + "type": "object" + } + }, + "required": [ + "id", + "name", + "metadata" + ] + }, + "Profile": { + "type": "object", + "properties": { + "username": { + "type": "string", + "nullable": true + }, + "id": { + "type": "number", + "nullable": true + }, + "userId": { + "type": "number" + }, + "uid": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organizationId": { + "type": "number", + "nullable": true + }, + "organization": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Organization" + } + ] + }, + "upId": { + "type": "string" + }, + "image": { + "type": "string" + }, + "brandColor": { + "type": "string" + }, + "darkBrandColor": { + "type": "string" + }, + "theme": { + "type": "string" + }, + "bookerLayouts": { + "type": "object" + } + }, + "required": [ + "username", + "id", + "organizationId", + "upId" + ] + }, + "Owner": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "avatarUrl": { + "type": "string", + "nullable": true + }, + "username": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string", + "nullable": true + }, + "weekStart": { + "type": "string" + }, + "brandColor": { + "type": "string", + "nullable": true + }, + "darkBrandColor": { + "type": "string", + "nullable": true + }, + "theme": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object" + }, + "defaultScheduleId": { + "type": "number", + "nullable": true + }, + "nonProfileUsername": { + "type": "string", + "nullable": true + }, + "profile": { + "$ref": "#/components/schemas/Profile" + } + }, + "required": [ + "id", + "username", + "name", + "weekStart", + "metadata", + "nonProfileUsername", + "profile" + ] + }, + "Schedule": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "timeZone": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "timeZone" + ] + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string", + "nullable": true + }, + "weekStart": { + "type": "string" + }, + "organizationId": { + "type": "number" + }, + "avatarUrl": { + "type": "string", + "nullable": true + }, + "profile": { + "$ref": "#/components/schemas/Profile" + }, + "bookerUrl": { + "type": "string" + } + }, + "required": [ + "username", + "name", + "weekStart", + "profile", + "bookerUrl" + ] + }, + "PublicEventTypeOutput": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "eventName": { + "type": "string", + "nullable": true + }, + "slug": { + "type": "string" + }, + "isInstantEvent": { + "type": "boolean" + }, + "aiPhoneCallConfig": { + "type": "object" + }, + "schedulingType": { + "type": "object" + }, + "length": { + "type": "number" + }, + "locations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Location" + } + }, + "customInputs": { + "type": "array", + "items": { + "type": "object" + } + }, + "disableGuests": { + "type": "boolean" + }, + "metadata": { + "type": "object", + "nullable": true + }, + "lockTimeZoneToggleOnBookingPage": { + "type": "boolean" + }, + "requiresConfirmation": { + "type": "boolean" + }, + "requiresBookerEmailVerification": { + "type": "boolean" + }, + "recurringEvent": { + "type": "object" + }, + "price": { + "type": "number" + }, + "currency": { + "type": "string" + }, + "seatsPerTimeSlot": { + "type": "number", + "nullable": true + }, + "seatsShowAvailabilityCount": { + "type": "boolean", + "nullable": true + }, + "bookingFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BookingField" + } + }, + "team": { + "type": "object" + }, + "successRedirectUrl": { + "type": "string", + "nullable": true + }, + "workflows": { + "type": "array", + "items": { + "type": "object" + } + }, + "hosts": { + "type": "array", + "items": { + "type": "object" + } + }, + "owner": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Owner" + } + ] + }, + "schedule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Schedule" + } + ] + }, + "hidden": { + "type": "boolean" + }, + "assignAllTeamMembers": { + "type": "boolean" + }, + "bookerLayouts": { + "type": "object" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "entity": { + "type": "object" + }, + "isDynamic": { + "type": "boolean" + } + }, + "required": [ + "id", + "title", + "description", + "slug", + "isInstantEvent", + "length", + "locations", + "customInputs", + "disableGuests", + "metadata", + "lockTimeZoneToggleOnBookingPage", + "requiresConfirmation", + "requiresBookerEmailVerification", + "price", + "currency", + "seatsShowAvailabilityCount", + "bookingFields", + "workflows", + "hosts", + "owner", + "schedule", + "hidden", + "assignAllTeamMembers", + "users", + "entity", + "isDynamic" + ] + }, + "GetEventTypePublicOutput": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/PublicEventTypeOutput" + } + ] + } + }, + "required": [ + "status", + "data" + ] + }, "PublicEventType": { "type": "object", "properties": { @@ -3404,26 +3825,6 @@ "credentialId" ] }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "number" - }, - "name": { - "type": "string", - "nullable": true - }, - "email": { - "type": "string" - } - }, - "required": [ - "id", - "name", - "email" - ] - }, "GetBookingsDataEntry": { "type": "object", "properties": { @@ -3710,21 +4111,6 @@ "data" ] }, - "Location": { - "type": "object", - "properties": { - "optionValue": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "optionValue", - "value" - ] - }, "Response": { "type": "object", "properties": { diff --git a/packages/platform/atoms/hooks/usePublicEvent.tsx b/packages/platform/atoms/hooks/usePublicEvent.tsx index 39182f78b9..85789ac242 100644 --- a/packages/platform/atoms/hooks/usePublicEvent.tsx +++ b/packages/platform/atoms/hooks/usePublicEvent.tsx @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { shallow } from "zustand/shallow"; import { useBookerStore } from "@calcom/features/bookings/Booker/store"; -import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import { SUCCESS_STATUS, V2_ENDPOINTS } from "@calcom/platform-constants"; import type { PublicEventType } from "@calcom/platform-libraries"; import type { ApiResponse } from "@calcom/platform-types"; @@ -16,18 +16,22 @@ export const usePublicEvent = (props: { username: string; eventSlug: string }) = const isTeamEvent = useBookerStore((state) => state.isTeamEvent); const org = useBookerStore((state) => state.org); + const requestUsername = username ?? props.username; + const requestEventSlug = eventSlug ?? props.eventSlug; + const event = useQuery({ queryKey: [QUERY_KEY, username ?? props.username, eventSlug ?? props.eventSlug], queryFn: () => { return http - .get>("/events/public", { - params: { - username: username ?? props.username, - eventSlug: eventSlug ?? props.eventSlug, - isTeamEvent, - org: org ?? null, - }, - }) + .get>( + `/${V2_ENDPOINTS.eventTypes}/${requestUsername}/${requestEventSlug}/public`, + { + params: { + isTeamEvent, + org: org ?? null, + }, + } + ) .then((res) => { if (res.data.status === SUCCESS_STATUS) { return res.data.data; diff --git a/packages/platform/types/index.ts b/packages/platform/types/index.ts index 34ed18f55a..e117e6ff8e 100644 --- a/packages/platform/types/index.ts +++ b/packages/platform/types/index.ts @@ -1,7 +1,6 @@ export * from "./permissions"; export * from "./api"; export * from "./oauth-clients"; -export * from "./events"; export * from "./slots"; export * from "./calendars"; export * from "./schedules";