This reverts commit c0591f3cdb.
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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<ApiResponse<Partial<BookingResponse>>> {
|
||||
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<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
|
||||
@@ -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<ApiResponse<BookingResponse[]>> {
|
||||
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<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
|
||||
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<string | undefined> {
|
||||
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<OAuthRequestParams> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EventTypeOutput_2024_06_14> = 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();
|
||||
});
|
||||
});
|
||||
@@ -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()
|
||||
|
||||
@@ -160,7 +160,7 @@ class User {
|
||||
email!: string;
|
||||
}
|
||||
|
||||
export class GetBookingsDataEntry {
|
||||
class GetBookingsDataEntry {
|
||||
@IsInt()
|
||||
@ApiProperty()
|
||||
id!: number;
|
||||
|
||||
@@ -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],
|
||||
|
||||
-487
@@ -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<EventTypeOutput_2024_06_14> = 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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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<BookingRequest> {
|
||||
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<BookingRequest> {
|
||||
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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user