fix: v2 rescheduled booking with confirmation (#19833)

* fix: old bookings controller

* fix: new bookings controller

* fix: BookerPlatformWrapper pass rescheduledBy to BookerComponent

* rescheduleBy email

* fix test

* remove log

* centralize getOAuthUserEmail, examples app rescheduledBy

* revert

* finalize

* fix: old bookings rescheduling

* unify email validation

* update input description

* chore: move booker platform wrapper types

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
Lauris Skraucis
2025-03-11 20:04:59 +01:00
committed by GitHub
co-authored by Morgan Vernay
parent ff5c6de5da
commit 76b9b9b0fe
16 changed files with 315 additions and 220 deletions
@@ -14,8 +14,10 @@ import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { BillingService } from "@/modules/billing/services/billing.service";
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 { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { UsersRepository } from "@/modules/users/users.repository";
import {
Controller,
Post,
@@ -101,7 +103,8 @@ export class BookingsController_2024_04_15 {
private readonly billingService: BillingService,
private readonly config: ConfigService,
private readonly apiKeyRepository: ApiKeysRepository,
private readonly platformBookingsService: PlatformBookingsService
private readonly platformBookingsService: PlatformBookingsService,
private readonly usersRepository: UsersRepository
) {}
@Get("/")
@@ -352,6 +355,30 @@ export class BookingsController_2024_04_15 {
}
}
private async getOwnerIdRescheduledBooking(
request: Request,
platformClientId?: string
): Promise<number | undefined> {
if (
platformClientId &&
request.body.rescheduledBy &&
!request.body.rescheduledBy.includes(platformClientId)
) {
request.body.rescheduledBy = OAuthClientUsersService.getOAuthUserEmail(
platformClientId,
request.body.rescheduledBy
);
}
if (request.body.rescheduledBy) {
if (request.body.rescheduledBy !== request.body.responses.email) {
return (await this.usersRepository.findByEmail(request.body.rescheduledBy))?.id;
}
}
return undefined;
}
private async getOAuthClientIdFromEventType(eventTypeId: number): Promise<string | undefined> {
if (!eventTypeId) {
return undefined;
@@ -396,7 +423,9 @@ export class BookingsController_2024_04_15 {
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
const requestId = req.get("X-Request-Id");
const clone = { ...req };
const userId = (await this.getOwnerId(req)) ?? -1;
const userId = clone.body.rescheduleUid
? await this.getOwnerIdRescheduledBooking(req, oAuthClientId)
: await this.getOwnerId(req);
const oAuthParams = oAuthClientId
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
: DEFAULT_PLATFORM_PARAMS;
@@ -12,6 +12,7 @@ import {
CreateManagedUserData,
CreateManagedUserOutput,
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
@@ -140,7 +141,9 @@ describe("Managed user bookings 2024-04-15", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -193,7 +196,9 @@ describe("Managed user bookings 2024-04-15", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -220,7 +225,9 @@ describe("Managed user bookings 2024-04-15", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -444,11 +451,6 @@ describe("Managed user bookings 2024-04-15", () => {
});
});
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);
@@ -13,6 +13,8 @@ import {
Validate,
} from "class-validator";
import { RESCHEDULED_BY_DOCS } from "@calcom/platform-types";
class Location {
@IsString()
@ApiProperty()
@@ -76,6 +78,13 @@ export class CreateBookingInput_2024_04_15 {
@ApiPropertyOptional()
rescheduleUid?: string;
@IsOptional()
@ApiPropertyOptional({ description: RESCHEDULED_BY_DOCS })
@Validate((value: string) => !value || isEmail(value), {
message: "Invalid rescheduledBy email format",
})
rescheduledBy?: string;
@IsTimeZone()
@ApiProperty()
timeZone!: string;
@@ -94,6 +94,8 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
let emailsDisabledSetup: EmailSetup;
const authEmail = `confirm-emails-2024-08-13-admin-${randomString()}@api.com`;
let userEmailsEnabled = "";
const userEmailsDisabled = `confirm-emails-2024-08-13-user-${randomString()}@api.com`;
beforeAll(async () => {
const moduleRef = await withApiAuth(
@@ -141,8 +143,12 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
async function setupEnabledEmails() {
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
userEmailsEnabled = `confirm-emails-2024-08-13-user-${randomString()}+${
oAuthClientEmailsEnabled.id
}@api.com`;
const user = await userRepositoryFixture.create({
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
email: userEmailsEnabled,
platformOAuthClients: {
connect: {
id: oAuthClientEmailsEnabled.id,
@@ -179,7 +185,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
const user = await userRepositoryFixture.create({
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
email: userEmailsDisabled,
platformOAuthClients: {
connect: {
id: oAuthClientEmailsDisabled.id,
@@ -282,9 +288,6 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
expect(responseBody.data).toBeDefined();
expect(AttendeeScheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
expect(OrganizerScheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
emailsDisabledSetup.rescheduledBookingUid = responseBody.data.uid;
});
});
@@ -338,10 +341,6 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeDeclinedEmail.prototype.getHtml).not.toHaveBeenCalled();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
emailsDisabledSetup.rescheduledBookingUid = responseBody.data.uid;
});
});
});
@@ -351,117 +350,176 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
jest.clearAllMocks();
});
it("should send an email when creating a booking that requires confirmation", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
eventTypeId: emailsEnabledSetup.eventTypeId,
attendee: {
name: "Mr Proper",
email: "mr_proper@gmail.com",
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
customField: "customValue",
},
metadata: {
userId: "100",
},
};
describe("confirming booking that requires confirmation and rescheduling it", () => {
it("should send an email when creating a booking that requires confirmation", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
eventTypeId: emailsEnabledSetup.eventTypeId,
attendee: {
name: "Mr Proper",
email: "mr_proper@gmail.com",
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
customField: "customValue",
},
metadata: {
userId: "100",
},
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("pending");
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibly recurring bookings"
);
}
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("pending");
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});
it("should send an email when confirming a booking", async () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/confirm`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeScheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerScheduledEmail.prototype.getHtml).toHaveBeenCalled();
});
});
describe("rescheduling emails", () => {
it("should send confirmation emails when organizer reschedules a booking that requires confirmation", async () => {
const body: RescheduleBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 14, 30, 0)).toISOString(),
rescheduledBy: userEmailsEnabled,
};
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/reschedule`)
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("accepted");
expect(AttendeeRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).not.toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).not.toHaveBeenCalled();
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});
it("should send requested rescheduling emails when attendee rescheduling a booking that requires confirmation", async () => {
const body: RescheduleBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 9, 0, 0)).toISOString(),
};
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.rescheduledBookingUid}/reschedule`)
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("pending");
expect(AttendeeRescheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
expect(OrganizerRescheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});
});
});
it("should send an email when confirming a booking", async () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/confirm`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeScheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerScheduledEmail.prototype.getHtml).toHaveBeenCalled();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
});
});
describe("declining booking that requires confirmation", () => {
it("should send an email when creating a booking that requires confirmation", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
eventTypeId: emailsEnabledSetup.eventTypeId,
attendee: {
name: "Mr Proper",
email: "mr_proper@gmail.com",
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
customField: "customValue",
},
metadata: {
userId: "100",
},
};
it("should send an email when creating a booking that requires confirmation", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
eventTypeId: emailsEnabledSetup.eventTypeId,
attendee: {
name: "Mr Proper",
email: "mr_proper@gmail.com",
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
customField: "customValue",
},
metadata: {
userId: "100",
},
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("pending");
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
if (responseDataIsBooking(responseBody.data)) {
expect(responseBody.data.status).toEqual("pending");
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibly recurring bookings"
);
}
});
});
it("should send an email when declining a booking", async () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/decline`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeDeclinedEmail.prototype.getHtml).toHaveBeenCalled();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
});
it("should send an email when declining a booking", async () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/decline`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeDeclinedEmail.prototype.getHtml).toHaveBeenCalled();
});
});
});
});
@@ -7,6 +7,7 @@ import {
CreateManagedUserData,
CreateManagedUserOutput,
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
@@ -149,7 +150,9 @@ describe("Managed user bookings 2024-08-13", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -202,7 +205,9 @@ describe("Managed user bookings 2024-08-13", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -229,7 +234,9 @@ describe("Managed user bookings 2024-08-13", () => {
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.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.accessToken).toBeDefined();
expect(responseBody.data.refreshToken).toBeDefined();
@@ -470,11 +477,6 @@ describe("Managed user bookings 2024-08-13", () => {
});
});
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);
@@ -8,7 +8,9 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_20
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 { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@@ -75,7 +77,8 @@ export class InputBookingsService_2024_08_13 {
private readonly config: ConfigService,
private readonly apiKeyRepository: ApiKeysRepository,
private readonly bookingSeatRepository: BookingSeatRepository,
private readonly platformBookingsService: PlatformBookingsService
private readonly platformBookingsService: PlatformBookingsService,
private readonly usersRepository: UsersRepository
) {}
async createBookingRequest(
@@ -314,7 +317,24 @@ export class InputBookingsService_2024_08_13 {
);
const newRequest = { ...request };
const userId = (await this.createBookingRequestOwnerId(request)) ?? undefined;
let userId: number | undefined = undefined;
if (
oAuthClientParams &&
request.body.rescheduledBy &&
!request.body.rescheduledBy.includes(oAuthClientParams.platformClientId)
) {
request.body.rescheduledBy = OAuthClientUsersService.getOAuthUserEmail(
oAuthClientParams.platformClientId,
request.body.rescheduledBy
);
}
if (request.body.rescheduledBy) {
if (request.body.rescheduledBy !== bodyTransformed.responses.email) {
userId = (await this.usersRepository.findByEmail(request.body.rescheduledBy))?.id;
}
}
const location = await this.getRescheduleBookingLocation(bookingUid);
if (oAuthClientParams) {
@@ -9,7 +9,6 @@ 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
@@ -25,7 +24,7 @@ export class PlatformBookingsService {
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 oAuthUserEmail = OAuthClientUsersService.getOAuthUserEmail(platformClientId, attendeeEmail);
const oAuthUser = await this.usersRepository.findByEmail(oAuthUserEmail);
if (oAuthUser) {
return oAuthUserEmail;
@@ -7,6 +7,7 @@ import { Locales } from "@/lib/enums/locales";
import { CreateManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
import { GetManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-user.output";
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
import { UsersModule } from "@/modules/users/users.module";
@@ -218,7 +219,9 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
expect(responseBody.data.user.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
);
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
expect(responseBody.data.user.weekStart).toEqual(requestBody.weekStart);
@@ -297,7 +300,7 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.email).toEqual(
getOAuthUserEmail(oAuthClientEventTypesDisabled.id, requestBody.email)
OAuthClientUsersService.getOAuthUserEmail(oAuthClientEventTypesDisabled.id, requestBody.email)
);
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
@@ -518,7 +521,9 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.email).toEqual(getOAuthUserEmail(oAuthClient.id, userEmail));
expect(responseBody.data.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, userEmail)
);
});
it(`/PUT/:id`, async () => {
@@ -536,7 +541,9 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.email).toEqual(getOAuthUserEmail(oAuthClient.id, userUpdatedEmail));
expect(responseBody.data.email).toEqual(
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, userUpdatedEmail)
);
expect(responseBody.data.locale).toEqual(Locales.PT_BR);
});
@@ -548,13 +555,6 @@ describe("OAuth Client Users Endpoints", () => {
.expect(200);
});
function getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
const [username, emailDomain] = userEmail.split("@");
const email = `${username}+${oAuthClientId}@${emailDomain}`;
return email;
}
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await oauthClientRepositoryFixture.delete(oAuthClientEventTypesDisabled.id);
@@ -36,7 +36,7 @@ export class OAuthClientUsersService {
"You cannot create a managed user outside of an organization - the OAuth client does not belong to any organization."
);
} else {
const email = this.getOAuthUserEmail(oAuthClientId, body.email);
const email = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, body.email);
user = (
await createNewUsersConnectToOrgIfExists({
invitations: [
@@ -98,7 +98,7 @@ export class OAuthClientUsersService {
}
async getExistingUserByEmail(oAuthClientId: string, email: string) {
const oAuthEmail = this.getOAuthUserEmail(oAuthClientId, email);
const oAuthEmail = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, email);
return await this.userRepository.findByEmail(oAuthEmail);
}
@@ -121,7 +121,7 @@ export class OAuthClientUsersService {
async updateOAuthClientUser(oAuthClientId: string, userId: number, body: UpdateManagedUserInput) {
if (body.email) {
const emailWithOAuthId = this.getOAuthUserEmail(oAuthClientId, body.email);
const emailWithOAuthId = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, body.email);
body.email = emailWithOAuthId;
const newUsername = slugify(emailWithOAuthId);
await this.userRepository.updateUsername(userId, newUsername);
@@ -130,7 +130,7 @@ export class OAuthClientUsersService {
return this.userRepository.update(userId, body);
}
getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
static getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
const [username, emailDomain] = userEmail.split("@");
return `${username}+${oAuthClientId}@${emailDomain}`;
}