Skip to content

Commit

Permalink
refactor: v2 bookings (calcom#16200)
Browse files Browse the repository at this point in the history
* chore: version existing bookings as 2024-04-15

* feat: initialize bookings version 2024-08-13

* feat: Create and reschedule booking inputs logic

* feat: create booking

* refactor: create booking response

* feat: reschedule booking

* chore: update language input

* feat: recurring booking

* refactor: add booking status in response

* refactor: recurring bookings

* feat: get booking by uid

* wip: get event types

* feat: fetch by multiple status filters and sort

* feat: fetch by teamId, teamIds, eventTypeId, eventTypeIds

* wip: filter by attendee email

* feat: filter by attendee email

* feat: filter by attendee name

* feat: date range filter

* chore: format get bookings output

* chore: finish main merge

* feat: handle instant bookings

* refactor: separate reschedule endpoint

* feat: cancel endpoint

* feat: mark absent host or attendees

* chore: dont expose metadata for now

* chore: add hostId to response

* fix: metadata

* feat: bill bookings

* feat: cancellationReason

* feat: rescheduling reason

* handle already busy booking error

* test: create new booking

* fix: handleNewRecurringBooking ignoring noEmail

* test: recurring bookings

* test: get individual bookings

* fix: cancel email sent if arePlatformEmailsEnabled=false but platformClientId is undefined

* tests: cancel, reschedule, mark absent

* fix: generateIcsFile null pointer exception

* cancel test

* error msg improve

* tests: team event type creation and teamId, teamIds filters

* test: cancel recurring booking

* refactor: make hosts and attendees an array

* sort by asc start

* simplify

* refactor: absent

* fix: make work with api key

* test

* ts remove any

* feat: BookingUidGuard

* fix: recurring booking no email

* fix: legacy bookings recurring noEmail

* add swagger

* retrigger build

* fix: atom booker work with v2

* docs: exclude old controller from docs

* refactor: make eventTypeIds and teamIds getBookings query params comma separated string

* docs: swagger for get bookings query

* swagger docs

* swagger docs

* docs: document authorization header

* refactor: remove unused attendee variable

* refactor: remove unused check

* refactor: remove unused attendee variable

* refactor: spelling

* use published platform libraries

* fix: ci

* fix: ci

* fix: ci

* fix: ci

* cleanup script platform types

* fix: use libraries from npm

* chore: set test env vapid keys

* fix: event type tests

* fix: remove location from system fields

* fix legacy event types

* Revert "fix legacy event types"

This reverts commit e64b473.

* Revert "fix: remove location from system fields"

This reverts commit bee9a15.

* Revert "fix: event type tests"

This reverts commit fab1cb0.

* update libraries

* fix: increase node space for ci runner

* fix: increase node space for ci runner

* fix: increase node space for ci runner

* readd swagger

* ci

* ci

* refactor: increase idle worker memory jest e2e

* fixup! refactor: increase idle worker memory jest e2e

* fixup! fixup! refactor: increase idle worker memory jest e2e

* refactor: split bookings e2e into smaller e2e files

* fixup! refactor: split bookings e2e into smaller e2e files

* fixup! fixup! refactor: split bookings e2e into smaller e2e files

* fixup! fixup! fixup! refactor: split bookings e2e into smaller e2e files

* fixup! Merge branch 'main' into v2-refactor-bookings

* revert event types service

* fix: remove resetModule, maxWorker 2 jest e2e config

---------

Co-authored-by: Morgan Vernay <[email protected]>
Co-authored-by: Morgan <[email protected]>
  • Loading branch information
3 people authored Sep 23, 2024
1 parent 3371602 commit 0eabe7f
Show file tree
Hide file tree
Showing 60 changed files with 4,818 additions and 654 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-api-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
IS_E2E: true
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
NODE_OPTIONS: --max-old-space-size=4096
NODE_OPTIONS: --max-old-space-size=29000
REDIS_URL: "redis://localhost:6379"
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_API_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
Expand Down
4 changes: 3 additions & 1 deletion apps/api/v2/jest-e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@
"^.+\\.(t|j)s$": "ts-jest"
},
"setupFiles": ["<rootDir>/test/setEnvVars.ts"],
"reporters": ["default", "jest-summarizing-reporter"]
"reporters": ["default", "jest-summarizing-reporter"],
"workerIdleMemoryLimit": "512MB",
"maxWorkers": 2
}
4 changes: 2 additions & 2 deletions apps/api/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"test:watch": "yarn dev:build && jest --watch",
"test:cov": "yarn dev:build && jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "yarn dev:build && jest --runInBand --detectOpenHandles --forceExit --config ./jest-e2e.json",
"test:e2e": "yarn dev:build && NODE_OPTIONS='--max_old_space_size=8192' jest --ci --forceExit --config ./jest-e2e.json",
"test:e2e:watch": "yarn dev:build && jest --runInBand --detectOpenHandles --forceExit --config ./jest-e2e.json --watch",
"prisma": "yarn workspace @calcom/prisma prisma",
"generate-schemas": "yarn prisma generate && yarn prisma format",
Expand All @@ -28,7 +28,7 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/[email protected].36",
"@calcom/platform-libraries": "npm:@calcom/[email protected].37",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/[email protected]",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BookingsController } from "@/ee/bookings/controllers/bookings.controller";
import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller";
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
import { BillingModule } from "@/modules/billing/billing.module";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
Expand All @@ -12,6 +12,6 @@ import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, RedisModule, TokensModule, BillingModule],
providers: [TokensRepository, OAuthFlowService, OAuthClientRepository, ApiKeyRepository],
controllers: [BookingsController],
controllers: [BookingsController_2024_04_15],
})
export class BookingsModule {}
export class BookingsModule_2024_04_15 {}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input";
import { GetBookingOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/get-booking.output";
import { GetBookingsOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/get-bookings.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";
Expand All @@ -24,7 +24,7 @@ import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { handleNewBooking } from "@calcom/platform-libraries";
import { ApiSuccessResponse, ApiResponse, ApiErrorResponse } from "@calcom/platform-types";

describe("Bookings Endpoints", () => {
describe("Bookings Endpoints 2024-04-15", () => {
describe("User Authenticated", () => {
let app: INestApplication;

Expand Down Expand Up @@ -111,7 +111,7 @@ describe("Bookings Endpoints", () => {
guests: [],
};

const body: CreateBookingInput = {
const body: CreateBookingInput_2024_04_15 = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
Expand Down Expand Up @@ -166,7 +166,7 @@ describe("Bookings Endpoints", () => {
guests: [],
};

const body: CreateBookingInput = {
const body: CreateBookingInput_2024_04_15 = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
Expand Down Expand Up @@ -209,7 +209,7 @@ describe("Bookings Endpoints", () => {
guests: [],
};

const body: CreateBookingInput = {
const body: CreateBookingInput_2024_04_15 = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
Expand Down Expand Up @@ -266,7 +266,7 @@ describe("Bookings Endpoints", () => {
guests: [],
};

const body: CreateBookingInput = {
const body: CreateBookingInput_2024_04_15 = {
rescheduleUid: createdBooking.uid,
start: newBookingStart,
end: newBookingEnd,
Expand Down Expand Up @@ -305,7 +305,7 @@ describe("Bookings Endpoints", () => {
return request(app.getHttpServer())
.get("/v2/bookings?filters[status]=upcoming")
.then((response) => {
const responseBody: GetBookingsOutput = response.body;
const responseBody: GetBookingsOutput_2024_04_15 = response.body;

expect(responseBody.data.bookings.length).toEqual(2);
const fetchedBooking = responseBody.data.bookings.find(
Expand All @@ -329,7 +329,7 @@ describe("Bookings Endpoints", () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${createdBooking.uid}`)
.then((response) => {
const responseBody: GetBookingOutput = response.body;
const responseBody: GetBookingOutput_2024_04_15 = response.body;
const bookingInfo = responseBody.data;

expect(responseBody.status).toEqual(SUCCESS_STATUS);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { CreateRecurringBookingInput } from "@/ee/bookings/inputs/create-recurring-booking.input";
import { MarkNoShowInput } from "@/ee/bookings/inputs/mark-no-show.input";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { MarkNoShowOutput } from "@/ee/bookings/outputs/mark-no-show.output";
import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input";
import { CreateRecurringBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-recurring-booking.input";
import { MarkNoShowInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/mark-no-show.input";
import { GetBookingOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/get-booking.output";
import { GetBookingsOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/get-bookings.output";
import { MarkNoShowOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/mark-no-show.output";
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { VERSION_2024_04_15, VERSION_2024_06_11, VERSION_2024_06_14 } from "@/lib/api-versions";
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
Expand All @@ -31,7 +31,7 @@ import {
UseGuards,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { ApiQuery, ApiTags as DocsTags } from "@nestjs/swagger";
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { Request } from "express";
import { NextApiRequest } from "next/types";
Expand All @@ -40,10 +40,10 @@ import { v4 as uuidv4 } from "uuid";
import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
import {
handleNewRecurringBooking,
handleNewBooking,
BookingResponse,
HttpError,
handleNewRecurringBooking,
handleInstantMeeting,
handleMarkNoShow,
getAllUserBookings,
Expand All @@ -52,7 +52,11 @@ import {
getBookingForReschedule,
ErrorCode,
} from "@calcom/platform-libraries";
import { GetBookingsInput, CancelBookingInput, Status } from "@calcom/platform-types";
import {
GetBookingsInput_2024_04_15,
CancelBookingInput_2024_04_15,
Status_2024_04_15,
} from "@calcom/platform-types";
import { ApiResponse } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";

Expand Down Expand Up @@ -80,11 +84,11 @@ const DEFAULT_PLATFORM_PARAMS = {

@Controller({
path: "/v2/bookings",
version: API_VERSIONS_VALUES,
version: [VERSION_2024_04_15, VERSION_2024_06_11, VERSION_2024_06_14],
})
@UseGuards(PermissionsGuard)
@DocsTags("Bookings")
export class BookingsController {
@DocsExcludeController(true)
export class BookingsController_2024_04_15 {
private readonly logger = new Logger("BookingsController");

constructor(
Expand All @@ -99,16 +103,16 @@ export class BookingsController {
@Get("/")
@UseGuards(ApiAuthGuard)
@Permissions([BOOKING_READ])
@ApiQuery({ name: "filters[status]", enum: Status, required: true })
@ApiQuery({ name: "filters[status]", enum: Status_2024_04_15, required: true })
@ApiQuery({ name: "limit", type: "number", required: false })
@ApiQuery({ name: "cursor", type: "number", required: false })
async getBookings(
@GetUser() user: User,
@Query() queryParams: GetBookingsInput
): Promise<GetBookingsOutput> {
@Query() queryParams: GetBookingsInput_2024_04_15
): Promise<GetBookingsOutput_2024_04_15> {
const { filters, cursor, limit } = queryParams;
const bookings = await getAllUserBookings({
bookingListingByStatus: filters.status,
bookingListingByStatus: [filters.status],
skip: cursor ?? 0,
take: limit ?? 10,
filters,
Expand All @@ -125,7 +129,7 @@ export class BookingsController {
}

@Get("/:bookingUid")
async getBooking(@Param("bookingUid") bookingUid: string): Promise<GetBookingOutput> {
async getBooking(@Param("bookingUid") bookingUid: string): Promise<GetBookingOutput_2024_04_15> {
const { bookingInfo } = await getBookingInfo(bookingUid);

if (!bookingInfo) {
Expand Down Expand Up @@ -155,7 +159,7 @@ export class BookingsController {
@Post("/")
async createBooking(
@Req() req: BookingRequest,
@Body() body: CreateBookingInput,
@Body() body: CreateBookingInput_2024_04_15,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<Partial<BookingResponse>>> {
const oAuthClientId = clientId?.toString();
Expand Down Expand Up @@ -186,7 +190,7 @@ export class BookingsController {
async cancelBooking(
@Req() req: BookingRequest,
@Param("bookingId") bookingId: string,
@Body() _: CancelBookingInput,
@Body() _: CancelBookingInput_2024_04_15,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
const oAuthClientId = clientId?.toString();
Expand Down Expand Up @@ -219,9 +223,9 @@ export class BookingsController {
@UseGuards(ApiAuthGuard)
async markNoShow(
@GetUser("id") userId: number,
@Body() body: MarkNoShowInput,
@Body() body: MarkNoShowInput_2024_04_15,
@Param("bookingUid") bookingUid: string
): Promise<MarkNoShowOutput> {
): Promise<MarkNoShowOutput_2024_04_15> {
try {
const markNoShowResponse = await handleMarkNoShow({
bookingUid: bookingUid,
Expand All @@ -240,7 +244,7 @@ export class BookingsController {
@Post("/recurring")
async createRecurringBooking(
@Req() req: BookingRequest,
@Body() _: CreateRecurringBookingInput[],
@Body() _: CreateRecurringBookingInput_2024_04_15[],
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<BookingResponse[]>> {
const oAuthClientId = clientId?.toString();
Expand Down Expand Up @@ -278,7 +282,7 @@ export class BookingsController {
@Post("/instant")
async createInstantBooking(
@Req() req: BookingRequest,
@Body() _: CreateBookingInput,
@Body() _: CreateBookingInput_2024_04_15,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
const oAuthClientId = clientId?.toString();
Expand Down Expand Up @@ -369,7 +373,12 @@ export class BookingsController {
const oAuthParams = oAuthClientId
? await this.getOAuthClientsParams(oAuthClientId)
: DEFAULT_PLATFORM_PARAMS;
Object.assign(req, { userId, ...oAuthParams, platformBookingLocation });
Object.assign(req, {
userId,
...oAuthParams,
platformBookingLocation,
noEmail: !oAuthParams.arePlatformEmailsEnabled,
});
return req as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Response {
notes?: string;
}

export class CreateBookingInput {
export class CreateBookingInput_2024_04_15 {
@IsString()
@IsOptional()
end?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input";
import { IsBoolean, IsString, IsNumber, IsOptional } from "class-validator";

import type { AppsStatus } from "@calcom/platform-libraries";

export class CreateRecurringBookingInput extends CreateBookingInput {
export class CreateRecurringBookingInput_2024_04_15 extends CreateBookingInput_2024_04_15 {
@IsBoolean()
@IsOptional()
noEmail?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Attendee {
noShow!: boolean;
}

export class MarkNoShowInput {
export class MarkNoShowInput_2024_04_15 {
@IsBoolean()
@IsOptional()
noShowHost?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class EventType {
timeZone!: string | null;
}

class GetBookingData {
class GetBookingData_2024_04_15 {
@IsString()
title!: string;

Expand Down Expand Up @@ -159,15 +159,15 @@ class GetBookingData {
eventType!: EventType | null;
}

export class GetBookingOutput {
export class GetBookingOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;

@ApiProperty({
type: GetBookingData,
type: GetBookingData_2024_04_15,
})
@ValidateNested()
@Type(() => GetBookingData)
data!: GetBookingData;
@Type(() => GetBookingData_2024_04_15)
data!: GetBookingData_2024_04_15;
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class GetBookingsDataEntry {
rescheduled?: any;
}

class GetBookingsData {
class GetBookingsData_2024_04_15 {
@ValidateNested()
@Type(() => GetBookingsDataEntry)
@IsArray()
Expand All @@ -215,15 +215,15 @@ class GetBookingsData {
nextCursor!: number | null;
}

export class GetBookingsOutput {
export class GetBookingsOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;

@ApiProperty({
type: GetBookingsData,
type: GetBookingsData_2024_04_15,
})
@ValidateNested()
@Type(() => GetBookingsData)
data!: GetBookingsData;
@Type(() => GetBookingsData_2024_04_15)
data!: GetBookingsData_2024_04_15;
}
Loading

0 comments on commit 0eabe7f

Please sign in to comment.