diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts index 41b03b8592..d1ba1149e7 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/bookings.module.ts @@ -1,39 +1,17 @@ import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller"; -import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; -import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module"; -import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; -import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { BillingModule } from "@/modules/billing/billing.module"; import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository"; -import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service"; import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { RedisModule } from "@/modules/redis/redis.module"; import { TokensModule } from "@/modules/tokens/tokens.module"; import { TokensRepository } from "@/modules/tokens/tokens.repository"; -import { UsersModule } from "@/modules/users/users.module"; import { Module } from "@nestjs/common"; @Module({ - imports: [ - PrismaModule, - RedisModule, - TokensModule, - BillingModule, - UsersModule, - EventTypesModule_2024_04_15, - SchedulesModule_2024_04_15, - EventTypesModule_2024_06_14, - ], - providers: [ - TokensRepository, - OAuthFlowService, - OAuthClientRepository, - ApiKeysRepository, - OAuthClientUsersService, - PlatformBookingsService, - ], + imports: [PrismaModule, RedisModule, TokensModule, BillingModule], + providers: [TokensRepository, OAuthFlowService, OAuthClientRepository, ApiKeysRepository], controllers: [BookingsController_2024_04_15], }) export class BookingsModule_2024_04_15 {} diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts index 2a025ae11e..cbae0a5b7c 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts @@ -4,7 +4,6 @@ import { MarkNoShowInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/mark 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 { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key"; import { VERSION_2024_04_15, VERSION_2024_06_11, VERSION_2024_06_14 } from "@/lib/api-versions"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; @@ -100,8 +99,7 @@ export class BookingsController_2024_04_15 { private readonly oAuthClientRepository: OAuthClientRepository, private readonly billingService: BillingService, private readonly config: ConfigService, - private readonly apiKeyRepository: ApiKeysRepository, - private readonly platformBookingsService: PlatformBookingsService + private readonly apiKeyRepository: ApiKeysRepository ) {} @Get("/") @@ -168,8 +166,7 @@ export class BookingsController_2024_04_15 { @Headers(X_CAL_CLIENT_ID) clientId?: string, @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string ): Promise>> { - const oAuthClientId = - clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId)); + const oAuthClientId = clientId?.toString(); const { orgSlug, locationUrl } = body; req.headers["x-cal-force-slug"] = orgSlug; try { @@ -197,7 +194,7 @@ export class BookingsController_2024_04_15 { async cancelBooking( @Req() req: BookingRequest, @Param("bookingUid") bookingUid: string, - @Body() body: CancelBookingInput_2024_04_15, + @Body() _: CancelBookingInput_2024_04_15, @Headers(X_CAL_CLIENT_ID) clientId?: string, @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string ): Promise> { @@ -260,12 +257,11 @@ export class BookingsController_2024_04_15 { @Post("/recurring") async createRecurringBooking( @Req() req: BookingRequest, - @Body() body: CreateRecurringBookingInput_2024_04_15[], + @Body() _: CreateRecurringBookingInput_2024_04_15[], @Headers(X_CAL_CLIENT_ID) clientId?: string, @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string ): Promise> { - const oAuthClientId = - clientId?.toString() || (await this.getOAuthClientIdFromEventType(body[0]?.eventTypeId)); + const oAuthClientId = clientId?.toString(); try { const recurringEventId = uuidv4(); for (const recurringEvent of req.body) { @@ -300,12 +296,11 @@ export class BookingsController_2024_04_15 { @Post("/instant") async createInstantBooking( @Req() req: BookingRequest, - @Body() body: CreateBookingInput_2024_04_15, + @Body() _: CreateBookingInput_2024_04_15, @Headers(X_CAL_CLIENT_ID) clientId?: string, @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string ): Promise>>> { - const oAuthClientId = - clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId)); + const oAuthClientId = clientId?.toString(); req.userId = (await this.getOwnerId(req)) ?? -1; try { const instantMeeting = await handleInstantMeeting( @@ -351,17 +346,6 @@ export class BookingsController_2024_04_15 { } } - private async getOAuthClientIdFromEventType(eventTypeId: number): Promise { - if (!eventTypeId) { - return undefined; - } - const oAuthClientParams = await this.platformBookingsService.getOAuthClientParams(eventTypeId); - if (!oAuthClientParams) { - return undefined; - } - return oAuthClientParams.platformClientId; - } - private async getOAuthClientsParams(clientId: string, isEmbed = false): Promise { const res = { ...DEFAULT_PLATFORM_PARAMS }; @@ -412,27 +396,9 @@ export class BookingsController_2024_04_15 { noEmail: !oAuthParams.arePlatformEmailsEnabled, creationSource: CreationSource.API_V2, }; - if (oAuthClientId) { - await this.setPlatformAttendeesEmails(clone.body, oAuthClientId); - } return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams; } - async setPlatformAttendeesEmails(requestBody: any, oAuthClientId: string): Promise { - if (requestBody?.responses?.email) { - requestBody.responses.email = await this.platformBookingsService.getPlatformAttendeeEmail( - requestBody.responses.email, - oAuthClientId - ); - } - if (requestBody?.responses?.guests) { - requestBody.responses.guests = await this.platformBookingsService.getPlatformAttendeesEmails( - requestBody.responses.guests, - oAuthClientId - ); - } - } - private async createNextApiRecurringBookingRequest( req: BookingRequest, oAuthClientId?: string, @@ -459,9 +425,6 @@ export class BookingsController_2024_04_15 { noEmail: !oAuthParams.arePlatformEmailsEnabled, creationSource: CreationSource.API_V2, }); - if (oAuthClientId) { - await this.setPlatformAttendeesEmails(clone.body, oAuthClientId); - } return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams; } diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/managed-user-bookings.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/managed-user-bookings.e2e-spec.ts deleted file mode 100644 index 100ad8129b..0000000000 --- a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/managed-user-bookings.e2e-spec.ts +++ /dev/null @@ -1,461 +0,0 @@ -import { bootstrap } from "@/app"; -import { AppModule } from "@/app.module"; -import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input"; -import { - GetBookingsDataEntry, - GetBookingsOutput_2024_04_15, -} from "@/ee/bookings/2024-04-15/outputs/get-bookings.output"; -import { HttpExceptionFilter } from "@/filters/http-exception.filter"; -import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; -import { Locales } from "@/lib/enums/locales"; -import { - CreateManagedUserData, - CreateManagedUserOutput, -} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output"; -import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input"; -import { UsersModule } from "@/modules/users/users.module"; -import { INestApplication } from "@nestjs/common"; -import { NestExpressApplication } from "@nestjs/platform-express"; -import { Test } from "@nestjs/testing"; -import { PlatformOAuthClient, Team, User } from "@prisma/client"; -import * as request from "supertest"; -import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture"; -import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; -import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture"; -import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; -import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; -import { randomString } from "test/utils/randomString"; - -import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants"; -import { - ApiSuccessResponse, - CreateEventTypeInput_2024_06_14, - EventTypeOutput_2024_06_14, -} from "@calcom/platform-types"; - -const CLIENT_REDIRECT_URI = "http://localhost:4321"; - -describe("Managed user bookings 2024-04-15", () => { - let app: INestApplication; - - let oAuthClient: PlatformOAuthClient; - let organization: Team; - let userRepositoryFixture: UserRepositoryFixture; - let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; - let teamRepositoryFixture: TeamRepositoryFixture; - let profilesRepositoryFixture: ProfileRepositoryFixture; - let membershipsRepositoryFixture: MembershipRepositoryFixture; - - const platformAdminEmail = `managed-users-bookings-admin-${randomString()}@api.com`; - let platformAdmin: User; - - const managedUsersTimeZone = "Europe/Rome"; - const firstManagedUserEmail = `managed-user-bookings-2024-04-15-first-user@api.com`; - const secondManagedUserEmail = `managed-user-bookings-2024-04-15-second-user@api.com`; - const thirdManagedUserEmail = `managed-user-bookings-2024-04-15-third-user@api.com`; - - let firstManagedUser: CreateManagedUserData; - let secondManagedUser: CreateManagedUserData; - let thirdManagedUser: CreateManagedUserData; - - let firstManagedUserEventTypeId: number; - - let firstManagedUserBookingsCount = 0; - let secondManagedUserBookingsCount = 0; - let thirdManagedUserBookingsCount = 0; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [PrismaExceptionFilter, HttpExceptionFilter], - imports: [AppModule, UsersModule], - }).compile(); - - app = moduleRef.createNestApplication(); - bootstrap(app as NestExpressApplication); - - oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); - userRepositoryFixture = new UserRepositoryFixture(moduleRef); - teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); - profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef); - membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef); - - platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail }); - - organization = await teamRepositoryFixture.create({ - name: `oauth-client-users-organization-${randomString()}`, - isPlatform: true, - isOrganization: true, - }); - oAuthClient = await createOAuthClient(organization.id); - - await profilesRepositoryFixture.create({ - uid: "asd1qwwqeqw-asddsadasd", - username: platformAdminEmail, - organization: { connect: { id: organization.id } }, - user: { - connect: { id: platformAdmin.id }, - }, - }); - - await membershipsRepositoryFixture.create({ - role: "OWNER", - user: { connect: { id: platformAdmin.id } }, - team: { connect: { id: organization.id } }, - accepted: true, - }); - - await app.init(); - }); - - async function createOAuthClient(organizationId: number) { - const data = { - logo: "logo-url", - name: "name", - redirectUris: [CLIENT_REDIRECT_URI], - permissions: 1023, - }; - const secret = "secret"; - - const client = await oauthClientRepositoryFixture.create(organizationId, data, secret); - return client; - } - - it(`should create first managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: firstManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Alice Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - firstManagedUser = responseBody.data; - }); - - it("should create an event type for managed user", async () => { - const body: CreateEventTypeInput_2024_06_14 = { - title: "Managed user bookings first managed user event type", - slug: "managed-user-bookings-first-managed-user-event-type", - description: "Managed user bookings first managed user event type description", - lengthInMinutes: 30, - }; - - return request(app.getHttpServer()) - .post("/api/v2/event-types") - .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .send(body) - .expect(201) - .then(async (response) => { - const responseBody: ApiSuccessResponse = response.body; - const createdEventType = responseBody.data; - expect(createdEventType).toHaveProperty("id"); - expect(createdEventType.title).toEqual(body.title); - expect(createdEventType.description).toEqual(body.description); - expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes); - expect(createdEventType.ownerId).toEqual(firstManagedUser.user.id); - firstManagedUserEventTypeId = createdEventType.id; - }); - }); - - it(`should create second managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: secondManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Bob Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - secondManagedUser = responseBody.data; - }); - - it(`should create third managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: thirdManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Charlie Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - thirdManagedUser = responseBody.data; - }); - - describe("bookings using original emails", () => { - it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => { - const body: CreateBookingInput_2024_04_15 = { - start: "2040-05-21T09:30:00.000Z", - end: "2040-05-21T10:00:00.000Z", - eventTypeId: firstManagedUserEventTypeId, - timeZone: "Europe/London", - language: "en", - metadata: {}, - hashedLink: "", - responses: { - name: secondManagedUser.user.name || "unknown-name", - email: secondManagedUserEmail, - location: { - value: "attendeeInPerson", - optionValue: "rome", - }, - notes: "test", - guests: [], - }, - }; - - await request(app.getHttpServer()).post("/v2/bookings").send(body).expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getFirstManagedUserBookings.body; - const firstManagedUserBookings: GetBookingsDataEntry[] = - firstManagedUserBookingsResponseBody.data.bookings; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getSecondManagedUserBookings.body; - const secondManagedUserBookings: GetBookingsDataEntry[] = - secondManagedUserBookingsResponseBody.data.bookings; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - }); - - it("managed user should be booked by managed user attendee and managed user as a guest and booking shows up in all three users' bookings", async () => { - const body: CreateBookingInput_2024_04_15 = { - start: "2040-05-21T10:30:00.000Z", - end: "2040-05-21T11:00:00.000Z", - eventTypeId: firstManagedUserEventTypeId, - timeZone: "Europe/London", - language: "en", - metadata: {}, - hashedLink: "", - responses: { - name: thirdManagedUser.user.name || "unknown-name", - email: thirdManagedUserEmail, - location: { - value: "attendeeInPerson", - optionValue: "rome", - }, - notes: "test", - guests: [secondManagedUserEmail], - }, - }; - - await request(app.getHttpServer()).post("/v2/bookings").send(body).expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - thirdManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getFirstManagedUserBookings.body; - const firstManagedUserBookings: GetBookingsDataEntry[] = - firstManagedUserBookingsResponseBody.data.bookings; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getSecondManagedUserBookings.body; - const secondManagedUserBookings: GetBookingsDataEntry[] = - secondManagedUserBookingsResponseBody.data.bookings; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - - const getThirdManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${thirdManagedUser.accessToken}`) - .expect(200); - const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getThirdManagedUserBookings.body; - const thirdManagedUserBookings: GetBookingsDataEntry[] = - thirdManagedUserBookingsResponseBody.data.bookings; - expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount); - }); - }); - - describe("bookings using OAuth client emails", () => { - it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => { - const body: CreateBookingInput_2024_04_15 = { - start: "2040-05-21T12:00:00.000Z", - end: "2040-05-21T12:30:00.000Z", - eventTypeId: firstManagedUserEventTypeId, - timeZone: "Europe/London", - language: "en", - metadata: {}, - hashedLink: "", - responses: { - name: secondManagedUser.user.name || "unknown-name", - email: secondManagedUser.user.email, - location: { - value: "attendeeInPerson", - optionValue: "rome", - }, - notes: "test", - guests: [], - }, - }; - - await request(app.getHttpServer()).post("/v2/bookings").send(body).expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getFirstManagedUserBookings.body; - const firstManagedUserBookings: GetBookingsDataEntry[] = - firstManagedUserBookingsResponseBody.data.bookings; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getSecondManagedUserBookings.body; - const secondManagedUserBookings: GetBookingsDataEntry[] = - secondManagedUserBookingsResponseBody.data.bookings; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - }); - - it("managed user should be booked by managed user attendee and managed user as a guest and booking shows up in all three users' bookings", async () => { - const body: CreateBookingInput_2024_04_15 = { - start: "2040-05-21T13:00:00.000Z", - end: "2040-05-21T13:30:00.000Z", - eventTypeId: firstManagedUserEventTypeId, - timeZone: "Europe/London", - language: "en", - metadata: {}, - hashedLink: "", - responses: { - name: thirdManagedUser.user.name || "unknown-name", - email: thirdManagedUser.user.email, - location: { - value: "attendeeInPerson", - optionValue: "rome", - }, - notes: "test", - guests: [secondManagedUser.user.email], - }, - }; - - await request(app.getHttpServer()).post("/v2/bookings").send(body).expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - thirdManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getFirstManagedUserBookings.body; - const firstManagedUserBookings: GetBookingsDataEntry[] = - firstManagedUserBookingsResponseBody.data.bookings; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getSecondManagedUserBookings.body; - const secondManagedUserBookings: GetBookingsDataEntry[] = - secondManagedUserBookingsResponseBody.data.bookings; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - - const getThirdManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set("Authorization", `Bearer ${thirdManagedUser.accessToken}`) - .expect(200); - const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_04_15 = - getThirdManagedUserBookings.body; - const thirdManagedUserBookings: GetBookingsDataEntry[] = - thirdManagedUserBookingsResponseBody.data.bookings; - expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount); - }); - }); - - function getOAuthUserEmail(oAuthClientId: string, userEmail: string) { - const [username, emailDomain] = userEmail.split("@"); - return `${username}+${oAuthClientId}@${emailDomain}`; - } - - afterAll(async () => { - await userRepositoryFixture.delete(firstManagedUser.user.id); - await userRepositoryFixture.delete(secondManagedUser.user.id); - await userRepositoryFixture.delete(thirdManagedUser.user.id); - await userRepositoryFixture.delete(platformAdmin.id); - await oauthClientRepositoryFixture.delete(oAuthClient.id); - await teamRepositoryFixture.delete(organization.id); - await app.close(); - }); -}); diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts b/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts index d9e4e93b8d..30512f2ab8 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts @@ -8,9 +8,8 @@ import { IsOptional, IsArray, IsObject, + IsEmail, ValidateNested, - isEmail, - Validate, } from "class-validator"; class Location { @@ -28,9 +27,7 @@ class Response { @ApiProperty() name!: string; - @Validate((value: string) => !value || isEmail(value), { - message: "Invalid response email", - }) + @IsEmail() @ApiProperty() email!: string; @@ -116,9 +113,8 @@ export class CreateBookingInput_2024_04_15 { @ApiPropertyOptional() seatReferenceUid?: string; - @ApiProperty({ type: Response }) - @ValidateNested() @Type(() => Response) + @ApiProperty({ type: Response }) responses!: Response; @IsString() diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/outputs/get-bookings.output.ts b/apps/api/v2/src/ee/bookings/2024-04-15/outputs/get-bookings.output.ts index 743863ecc4..91270c24fb 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/outputs/get-bookings.output.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/outputs/get-bookings.output.ts @@ -160,7 +160,7 @@ class User { email!: string; } -export class GetBookingsDataEntry { +class GetBookingsDataEntry { @IsInt() @ApiProperty() id!: number; diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts index 6585b7055b..42b732f33c 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts @@ -3,17 +3,12 @@ import { BookingsController_2024_08_13 } from "@/ee/bookings/2024-08-13/controll import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service"; import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service"; import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service"; -import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; -import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module"; -import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; -import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { BillingModule } from "@/modules/billing/billing.module"; import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module"; import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository"; import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository"; -import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service"; import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { RedisModule } from "@/modules/redis/redis.module"; @@ -23,22 +18,11 @@ import { UsersModule } from "@/modules/users/users.module"; import { Module } from "@nestjs/common"; @Module({ - imports: [ - PrismaModule, - RedisModule, - TokensModule, - BillingModule, - UsersModule, - BookingSeatModule, - SchedulesModule_2024_04_15, - EventTypesModule_2024_04_15, - EventTypesModule_2024_06_14, - ], + imports: [PrismaModule, RedisModule, TokensModule, BillingModule, UsersModule, BookingSeatModule], providers: [ TokensRepository, OAuthFlowService, OAuthClientRepository, - OAuthClientUsersService, BookingsService_2024_08_13, InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, @@ -46,7 +30,6 @@ import { Module } from "@nestjs/common"; EventTypesRepository_2024_06_14, BookingSeatRepository, ApiKeysRepository, - PlatformBookingsService, ], controllers: [BookingsController_2024_08_13], exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13], diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/managed-user-bookings.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/managed-user-bookings.e2e-spec.ts deleted file mode 100644 index 3fcd33e7c7..0000000000 --- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/managed-user-bookings.e2e-spec.ts +++ /dev/null @@ -1,487 +0,0 @@ -import { bootstrap } from "@/app"; -import { AppModule } from "@/app.module"; -import { HttpExceptionFilter } from "@/filters/http-exception.filter"; -import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; -import { Locales } from "@/lib/enums/locales"; -import { - CreateManagedUserData, - CreateManagedUserOutput, -} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output"; -import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input"; -import { UsersModule } from "@/modules/users/users.module"; -import { INestApplication } from "@nestjs/common"; -import { NestExpressApplication } from "@nestjs/platform-express"; -import { Test } from "@nestjs/testing"; -import { PlatformOAuthClient, Team, User } from "@prisma/client"; -import * as request from "supertest"; -import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture"; -import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture"; -import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; -import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture"; -import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture"; -import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; -import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; -import { randomString } from "test/utils/randomString"; - -import { - CAL_API_VERSION_HEADER, - SUCCESS_STATUS, - VERSION_2024_06_14, - VERSION_2024_08_13, -} from "@calcom/platform-constants"; -import { - ApiSuccessResponse, - BookingOutput_2024_08_13, - CreateBookingInput_2024_08_13, - CreateEventTypeInput_2024_06_14, - EventTypeOutput_2024_06_14, - GetBookingsOutput_2024_08_13, -} from "@calcom/platform-types"; - -const CLIENT_REDIRECT_URI = "http://localhost:4321"; - -describe("Managed user bookings 2024-08-13", () => { - let app: INestApplication; - - let oAuthClient: PlatformOAuthClient; - let organization: Team; - let userRepositoryFixture: UserRepositoryFixture; - let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; - let teamRepositoryFixture: TeamRepositoryFixture; - let eventTypesRepositoryFixture: EventTypesRepositoryFixture; - let schedulesRepositoryFixture: SchedulesRepositoryFixture; - let profilesRepositoryFixture: ProfileRepositoryFixture; - let membershipsRepositoryFixture: MembershipRepositoryFixture; - - const platformAdminEmail = `managed-users-bookings-2024-08-13-admin-${randomString()}@api.com`; - let platformAdmin: User; - - const managedUsersTimeZone = "Europe/Rome"; - const firstManagedUserEmail = `managed-user-bookings-2024-08-13-first-user-${randomString()}@api.com`; - const secondManagedUserEmail = `managed-user-bookings-2024-08-13-second-user-${randomString()}@api.com`; - const thirdManagedUserEmail = `managed-user-bookings-2024-08-13-third-user-${randomString()}@api.com`; - - let firstManagedUser: CreateManagedUserData; - let secondManagedUser: CreateManagedUserData; - let thirdManagedUser: CreateManagedUserData; - - let firstManagedUserEventTypeId: number; - - let firstManagedUserBookingsCount = 0; - let secondManagedUserBookingsCount = 0; - let thirdManagedUserBookingsCount = 0; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [PrismaExceptionFilter, HttpExceptionFilter], - imports: [AppModule, UsersModule], - }).compile(); - - app = moduleRef.createNestApplication(); - bootstrap(app as NestExpressApplication); - - oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); - userRepositoryFixture = new UserRepositoryFixture(moduleRef); - teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); - eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef); - schedulesRepositoryFixture = new SchedulesRepositoryFixture(moduleRef); - profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef); - membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef); - - platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail }); - - organization = await teamRepositoryFixture.create({ - name: `oauth-client-users-organization-${randomString()}`, - isPlatform: true, - isOrganization: true, - }); - oAuthClient = await createOAuthClient(organization.id); - - await profilesRepositoryFixture.create({ - uid: "asd1qwwqeqw-asddsadasd", - username: platformAdminEmail, - organization: { connect: { id: organization.id } }, - user: { - connect: { id: platformAdmin.id }, - }, - }); - - await membershipsRepositoryFixture.create({ - role: "OWNER", - user: { connect: { id: platformAdmin.id } }, - team: { connect: { id: organization.id } }, - accepted: true, - }); - - await app.init(); - }); - - async function createOAuthClient(organizationId: number) { - const data = { - logo: "logo-url", - name: "name", - redirectUris: [CLIENT_REDIRECT_URI], - permissions: 1023, - }; - const secret = "secret"; - - const client = await oauthClientRepositoryFixture.create(organizationId, data, secret); - return client; - } - - it(`should create first managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: firstManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Alice Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - firstManagedUser = responseBody.data; - }); - - it("should create an event type for managed user", async () => { - const body: CreateEventTypeInput_2024_06_14 = { - title: "Managed user bookings first managed user event type", - slug: "managed-user-bookings-first-managed-user-event-type", - description: "Managed user bookings first managed user event type description", - lengthInMinutes: 30, - }; - - return request(app.getHttpServer()) - .post("/api/v2/event-types") - .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .send(body) - .expect(201) - .then(async (response) => { - const responseBody: ApiSuccessResponse = response.body; - const createdEventType = responseBody.data; - expect(createdEventType).toHaveProperty("id"); - expect(createdEventType.title).toEqual(body.title); - expect(createdEventType.description).toEqual(body.description); - expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes); - expect(createdEventType.ownerId).toEqual(firstManagedUser.user.id); - firstManagedUserEventTypeId = createdEventType.id; - }); - }); - - it(`should create second managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: secondManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Bob Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - secondManagedUser = responseBody.data; - }); - - it(`should create third managed user`, async () => { - const requestBody: CreateManagedUserInput = { - email: thirdManagedUserEmail, - timeZone: managedUsersTimeZone, - weekStart: "Monday", - timeFormat: 24, - locale: Locales.FR, - name: "Charlie Smith", - avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", - }; - - const response = await request(app.getHttpServer()) - .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) - .set("x-cal-secret-key", oAuthClient.secret) - .send(requestBody) - .expect(201); - - const responseBody: CreateManagedUserOutput = response.body; - expect(responseBody.status).toEqual(SUCCESS_STATUS); - expect(responseBody.data).toBeDefined(); - expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email)); - expect(responseBody.data.accessToken).toBeDefined(); - expect(responseBody.data.refreshToken).toBeDefined(); - - thirdManagedUser = responseBody.data; - }); - - describe("bookings using original emails", () => { - it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => { - const body: CreateBookingInput_2024_08_13 = { - start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(), - eventTypeId: firstManagedUserEventTypeId, - attendee: { - name: secondManagedUser.user.name!, - email: secondManagedUserEmail, - timeZone: secondManagedUser.user.timeZone, - language: secondManagedUser.user.locale, - }, - location: "https://meet.google.com/abc-def-ghi", - }; - - await request(app.getHttpServer()) - .post("/v2/bookings") - .send(body) - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getFirstManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getSecondManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const secondManagedUserBookings: BookingOutput_2024_08_13[] = - secondManagedUserBookingsResponseBody.data; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - }); - - it("managed user should be booked by managed user attendee and managed user as a guest and booking shows up in all three users' bookings", async () => { - const body: CreateBookingInput_2024_08_13 = { - start: new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString(), - eventTypeId: firstManagedUserEventTypeId, - attendee: { - name: thirdManagedUser.user.name!, - email: thirdManagedUserEmail, - timeZone: secondManagedUser.user.timeZone, - language: secondManagedUser.user.locale, - }, - guests: [secondManagedUserEmail], - location: "https://meet.google.com/abc-def-ghi", - }; - - await request(app.getHttpServer()) - .post("/v2/bookings") - .send(body) - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - thirdManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getFirstManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getSecondManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const secondManagedUserBookings: BookingOutput_2024_08_13[] = - secondManagedUserBookingsResponseBody.data; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - - const getThirdManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${thirdManagedUser.accessToken}`) - .expect(200); - - const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getThirdManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const thirdManagedUserBookings: BookingOutput_2024_08_13[] = thirdManagedUserBookingsResponseBody.data; - expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount); - }); - }); - - describe("bookings using OAuth client emails", () => { - it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => { - const body: CreateBookingInput_2024_08_13 = { - start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(), - eventTypeId: firstManagedUserEventTypeId, - attendee: { - name: secondManagedUser.user.name!, - email: secondManagedUser.user.email, - timeZone: secondManagedUser.user.timeZone, - language: secondManagedUser.user.locale, - }, - location: "https://meet.google.com/abc-def-ghi", - }; - - await request(app.getHttpServer()) - .post("/v2/bookings") - .send(body) - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getFirstManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getSecondManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const secondManagedUserBookings: BookingOutput_2024_08_13[] = - secondManagedUserBookingsResponseBody.data; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - }); - - it("managed user should be booked by managed user attendee and managed user as a guest and booking shows up in all three users' bookings", async () => { - const body: CreateBookingInput_2024_08_13 = { - start: new Date(Date.UTC(2030, 0, 8, 15, 30, 0)).toISOString(), - eventTypeId: firstManagedUserEventTypeId, - attendee: { - name: thirdManagedUser.user.name!, - email: thirdManagedUser.user.email, - timeZone: secondManagedUser.user.timeZone, - language: secondManagedUser.user.locale, - }, - guests: [secondManagedUser.user.email], - location: "https://meet.google.com/abc-def-ghi", - }; - - await request(app.getHttpServer()) - .post("/v2/bookings") - .send(body) - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .expect(201); - - firstManagedUserBookingsCount += 1; - secondManagedUserBookingsCount += 1; - thirdManagedUserBookingsCount += 1; - - const getFirstManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${firstManagedUser.accessToken}`) - .expect(200); - - const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getFirstManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data; - expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount); - - const getSecondManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${secondManagedUser.accessToken}`) - .expect(200); - - const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getSecondManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const secondManagedUserBookings: BookingOutput_2024_08_13[] = - secondManagedUserBookingsResponseBody.data; - expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount); - - const getThirdManagedUserBookings = await request(app.getHttpServer()) - .get("/v2/bookings") - .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) - .set("Authorization", `Bearer ${thirdManagedUser.accessToken}`) - .expect(200); - - const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 = - getThirdManagedUserBookings.body; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const thirdManagedUserBookings: BookingOutput_2024_08_13[] = thirdManagedUserBookingsResponseBody.data; - expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount); - }); - }); - - function getOAuthUserEmail(oAuthClientId: string, userEmail: string) { - const [username, emailDomain] = userEmail.split("@"); - return `${username}+${oAuthClientId}@${emailDomain}`; - } - - afterAll(async () => { - await userRepositoryFixture.delete(firstManagedUser.user.id); - await userRepositoryFixture.delete(secondManagedUser.user.id); - await userRepositoryFixture.delete(thirdManagedUser.user.id); - await userRepositoryFixture.delete(platformAdmin.id); - await oauthClientRepositoryFixture.delete(oAuthClient.id); - await teamRepositoryFixture.delete(organization.id); - await app.close(); - }); -}); diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts index b5ff991699..1fba30a154 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts @@ -1,7 +1,6 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings.repository"; import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service"; import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service"; -import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; import { BillingService } from "@/modules/billing/services/billing.service"; import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository"; @@ -68,8 +67,7 @@ export class BookingsService_2024_08_13 { private readonly prismaReadService: PrismaReadService, private readonly billingService: BillingService, private readonly usersService: UsersService, - private readonly usersRepository: UsersRepository, - private readonly platformBookingsService: PlatformBookingsService + private readonly usersRepository: UsersRepository ) {} async createBooking(request: Request, body: CreateBookingInput) { @@ -370,7 +368,7 @@ export class BookingsService_2024_08_13 { const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body); const bookingBefore = await this.bookingsRepository.getByUid(bookingUid); const platformClientParams = bookingBefore?.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId) + ? await this.inputService.getOAuthClientParams(bookingBefore.eventTypeId) : undefined; await handleMarkNoShow({ @@ -434,7 +432,7 @@ export class BookingsService_2024_08_13 { } const platformClientParams = booking.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId) + ? await this.inputService.getOAuthClientParams(booking.eventTypeId) : undefined; const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true; @@ -473,7 +471,7 @@ export class BookingsService_2024_08_13 { } const platformClientParams = booking.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId) + ? await this.inputService.getOAuthClientParams(booking.eventTypeId) : undefined; const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true; @@ -500,7 +498,7 @@ export class BookingsService_2024_08_13 { } const platformClientParams = booking.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId) + ? await this.inputService.getOAuthClientParams(booking.eventTypeId) : undefined; const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true; @@ -528,7 +526,7 @@ export class BookingsService_2024_08_13 { } const platformClientParams = booking.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId) + ? await this.inputService.getOAuthClientParams(booking.eventTypeId) : undefined; const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true; 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 49fb65ea66..72e4645b96 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 @@ -3,11 +3,11 @@ import { bookingResponsesSchema, seatedBookingDataSchema, } from "@/ee/bookings/2024-08-13/services/output.service"; -import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository"; +import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository"; import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service"; import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; import { Logger } from "@nestjs/common"; @@ -33,7 +33,7 @@ import { RescheduleBookingInput_2024_08_13, RescheduleSeatedBookingInput_2024_08_13, } from "@calcom/platform-types"; -import { EventType } from "@calcom/prisma/client"; +import { EventType, PlatformOAuthClient } from "@calcom/prisma/client"; type BookingRequest = NextApiRequest & { userId: number | undefined } & OAuthRequestParams; @@ -70,20 +70,20 @@ export class InputBookingsService_2024_08_13 { constructor( private readonly oAuthFlowService: OAuthFlowService, + private readonly oAuthClientRepository: OAuthClientRepository, private readonly eventTypesRepository: EventTypesRepository_2024_06_14, private readonly bookingsRepository: BookingsRepository_2024_08_13, private readonly config: ConfigService, private readonly apiKeyRepository: ApiKeysRepository, - private readonly bookingSeatRepository: BookingSeatRepository, - private readonly platformBookingsService: PlatformBookingsService + private readonly bookingSeatRepository: BookingSeatRepository ) {} async createBookingRequest( request: Request, body: CreateBookingInput_2024_08_13 | CreateInstantBookingInput_2024_08_13 ): Promise { - const oAuthClientParams = await this.platformBookingsService.getOAuthClientParams(body.eventTypeId); - const bodyTransformed = await this.transformInputCreateBooking(body, oAuthClientParams?.platformClientId); + const bodyTransformed = await this.transformInputCreateBooking(body); + const oAuthClientParams = await this.getOAuthClientParams(body.eventTypeId); const newRequest = { ...request }; const userId = (await this.createBookingRequestOwnerId(request)) ?? undefined; @@ -111,7 +111,30 @@ export class InputBookingsService_2024_08_13 { return newRequest as unknown as BookingRequest; } - async transformInputCreateBooking(inputBooking: CreateBookingInput_2024_08_13, platformClientId?: string) { + async getOAuthClientParams(eventTypeId: number) { + const eventType = await this.eventTypesRepository.getEventTypeById(eventTypeId); + + let oAuthClient: PlatformOAuthClient | null = null; + if (eventType?.userId) { + oAuthClient = await this.oAuthClientRepository.getByUserId(eventType.userId); + } else if (eventType?.teamId) { + oAuthClient = await this.oAuthClientRepository.getByTeamId(eventType.teamId); + } + + if (oAuthClient) { + return { + platformClientId: oAuthClient.id, + platformCancelUrl: oAuthClient.bookingCancelRedirectUri, + platformRescheduleUrl: oAuthClient.bookingRescheduleRedirectUri, + platformBookingUrl: oAuthClient.bookingRedirectUri, + arePlatformEmailsEnabled: oAuthClient.areEmailsEnabled, + }; + } + + return undefined; + } + + async transformInputCreateBooking(inputBooking: CreateBookingInput_2024_08_13) { const eventType = await this.eventTypesRepository.getEventTypeByIdWithOwnerAndTeam( inputBooking.eventTypeId ); @@ -128,17 +151,7 @@ export class InputBookingsService_2024_08_13 { ); const endTime = startTime.plus({ minutes: lengthInMinutes }); - const guests = - inputBooking.guests && platformClientId - ? await this.platformBookingsService.getPlatformAttendeesEmails(inputBooking.guests, platformClientId) - : inputBooking.guests; - const attendeeEmail = - inputBooking.attendee.email && platformClientId - ? await this.platformBookingsService.getPlatformAttendeeEmail( - inputBooking.attendee.email, - platformClientId - ) - : inputBooking.attendee.email; + const guests = inputBooking.guests; return { start: startTime.toISO(), @@ -150,13 +163,20 @@ export class InputBookingsService_2024_08_13 { hasHashedBookingLink: false, guests, // note(Lauris): responses with name and email are required by the handleNewBooking - responses: { - ...(inputBooking.bookingFieldsResponses || {}), - name: inputBooking.attendee.name, - email: attendeeEmail ?? "", - attendeePhoneNumber: inputBooking.attendee.phoneNumber, - guests, - }, + responses: inputBooking.bookingFieldsResponses + ? { + ...inputBooking.bookingFieldsResponses, + name: inputBooking.attendee.name, + email: inputBooking.attendee.email ?? "", + attendeePhoneNumber: inputBooking.attendee.phoneNumber, + guests, + } + : { + name: inputBooking.attendee.name, + email: inputBooking.attendee.email ?? "", + attendeePhoneNumber: inputBooking.attendee.phoneNumber, + guests, + }, }; } @@ -183,12 +203,9 @@ export class InputBookingsService_2024_08_13 { request: Request, body: CreateRecurringBookingInput_2024_08_13 ): Promise { - const oAuthClientParams = await this.platformBookingsService.getOAuthClientParams(body.eventTypeId); // note(Lauris): update to this.transformInputCreate when rescheduling is implemented - const bodyTransformed = await this.transformInputCreateRecurringBooking( - body, - oAuthClientParams?.platformClientId - ); + const bodyTransformed = await this.transformInputCreateRecurringBooking(body); + const oAuthClientParams = await this.getOAuthClientParams(body.eventTypeId); const newRequest = { ...request }; const userId = (await this.createBookingRequestOwnerId(request)) ?? undefined; @@ -214,10 +231,7 @@ export class InputBookingsService_2024_08_13 { return newRequest as unknown as BookingRequest; } - async transformInputCreateRecurringBooking( - inputBooking: CreateRecurringBookingInput_2024_08_13, - platformClientId?: string - ) { + async transformInputCreateRecurringBooking(inputBooking: CreateRecurringBookingInput_2024_08_13) { const eventType = await this.eventTypesRepository.getEventTypeByIdWithOwnerAndTeam( inputBooking.eventTypeId ); @@ -247,17 +261,7 @@ export class InputBookingsService_2024_08_13 { inputBooking.attendee.timeZone ); - const guests = - inputBooking.guests && platformClientId - ? await this.platformBookingsService.getPlatformAttendeesEmails(inputBooking.guests, platformClientId) - : inputBooking.guests; - const attendeeEmail = - inputBooking.attendee.email && platformClientId - ? await this.platformBookingsService.getPlatformAttendeeEmail( - inputBooking.attendee.email, - platformClientId - ) - : inputBooking.attendee.email; + const guests = inputBooking.guests; for (let i = 0; i < repeatsTimes; i++) { const endTime = startTime.plus({ minutes: eventType.length }); @@ -273,12 +277,14 @@ export class InputBookingsService_2024_08_13 { hasHashedBookingLink: false, guests, // note(Lauris): responses with name and email are required by the handleNewBooking - responses: { - ...(inputBooking.bookingFieldsResponses || {}), - name: inputBooking.attendee.name, - email: attendeeEmail, - guests, - }, + responses: inputBooking.bookingFieldsResponses + ? { + ...inputBooking.bookingFieldsResponses, + name: inputBooking.attendee.name, + email: inputBooking.attendee.email, + guests, + } + : { name: inputBooking.attendee.name, email: inputBooking.attendee.email, guests }, schedulingType: eventType.schedulingType, }); @@ -309,9 +315,7 @@ export class InputBookingsService_2024_08_13 { ? await this.transformInputRescheduleSeatedBooking(bookingUid, body) : await this.transformInputRescheduleBooking(bookingUid, body); - const oAuthClientParams = await this.platformBookingsService.getOAuthClientParams( - bodyTransformed.eventTypeId - ); + const oAuthClientParams = await this.getOAuthClientParams(bodyTransformed.eventTypeId); const newRequest = { ...request }; const userId = (await this.createBookingRequestOwnerId(request)) ?? undefined; @@ -497,7 +501,7 @@ export class InputBookingsService_2024_08_13 { } const oAuthClientParams = booking.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId) + ? await this.getOAuthClientParams(booking.eventTypeId) : undefined; const newRequest = { ...request }; diff --git a/apps/api/v2/src/ee/bookings/shared/platform-bookings.service.ts b/apps/api/v2/src/ee/bookings/shared/platform-bookings.service.ts deleted file mode 100644 index 5d1145ce0d..0000000000 --- a/apps/api/v2/src/ee/bookings/shared/platform-bookings.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; -import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository"; -import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service"; -import { UsersRepository } from "@/modules/users/users.repository"; -import { Injectable } from "@nestjs/common"; - -import { PlatformOAuthClient } from "@calcom/prisma/client"; - -@Injectable() -export class PlatformBookingsService { - constructor( - private readonly oAuthClientsUsersService: OAuthClientUsersService, - private readonly usersRepository: UsersRepository, - private readonly eventTypesRepository: EventTypesRepository_2024_06_14, - private readonly oAuthClientRepository: OAuthClientRepository - ) {} - - async getPlatformAttendeesEmails(guestsEmails: string[], platformClientId: string) { - return Promise.all( - guestsEmails.map((guestEmail) => this.getPlatformAttendeeEmail(guestEmail, platformClientId)) - ); - } - - async getPlatformAttendeeEmail(attendeeEmail: string, platformClientId: string) { - if (!attendeeEmail.includes(platformClientId)) { - // note(Lauris): we need to do this when managed user is booking another managed user and the managed user doing the booking entered their email without oAuth client id - // or if one of the guests added are managed users without oAuth client id in their email. - const oAuthUserEmail = this.oAuthClientsUsersService.getOAuthUserEmail(platformClientId, attendeeEmail); - const oAuthUser = await this.usersRepository.findByEmail(oAuthUserEmail); - if (oAuthUser) { - return oAuthUserEmail; - } - } - return attendeeEmail; - } - - async getOAuthClientParams(eventTypeId: number) { - const eventType = await this.eventTypesRepository.getEventTypeById(eventTypeId); - - let oAuthClient: PlatformOAuthClient | null = null; - if (eventType?.userId) { - oAuthClient = await this.oAuthClientRepository.getByUserId(eventType.userId); - } else if (eventType?.teamId) { - oAuthClient = await this.oAuthClientRepository.getByTeamId(eventType.teamId); - } - - if (oAuthClient) { - return { - platformClientId: oAuthClient.id, - platformCancelUrl: oAuthClient.bookingCancelRedirectUri, - platformRescheduleUrl: oAuthClient.bookingRescheduleRedirectUri, - platformBookingUrl: oAuthClient.bookingRedirectUri, - arePlatformEmailsEnabled: oAuthClient.areEmailsEnabled, - }; - } - - return undefined; - } -} diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output.ts index 0fcc1ffe32..54c2e40e0f 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output.ts @@ -5,7 +5,7 @@ import { IsEnum, IsNumber, IsString, ValidateNested } from "class-validator"; import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; -export class CreateManagedUserData { +class CreateManagedUserData { @ApiProperty({ type: ManagedUserOutput, }) diff --git a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts index 907b3a98fe..809eb37bbb 100644 --- a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts +++ b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts @@ -1,5 +1,6 @@ import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service"; import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service"; +import { OrganizationsTeamsService } from "@/modules/organizations/teams/index/services/organizations-teams.service"; import { TokensRepository } from "@/modules/tokens/tokens.repository"; import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input"; import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";