diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index b865d6c614..440563ef47 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -185,6 +185,7 @@ type InputEventType = { slotInterval?: number; minimumBookingNotice?: number; users?: { id: number }[]; + hosts?: { id: number }[]; schedulingType?: SchedulingType; beforeEventBuffer?: number; afterEventBuffer?: number; @@ -197,6 +198,7 @@ type InputBooking = { endTime: string; title?: string; status: BookingStatus; + attendees?: { email: string }[]; }; type InputHost = { @@ -789,6 +791,14 @@ describe("getSchedule", () => { id: 1, slotInterval: 45, schedulingType: "COLLECTIVE", + hosts: [ + { + id: 101, + }, + { + id: 102, + }, + ], }, // A default Event Type which this user owns { @@ -803,11 +813,21 @@ describe("getSchedule", () => { id: 101, schedules: [TestData.schedules.IstWorkHours], }, + { + ...TestData.users.example, + id: 102, + schedules: [TestData.schedules.IstWorkHours], + }, ], bookings: [ // Create a booking on our Collective Event Type { - // userId: XX, <- No owner since this is a Collective Event Type + userId: 101, + attendees: [ + { + email: "IntegrationTestUser102@example.com", + }, + ], eventTypeId: 1, status: "ACCEPTED", startTime: `${plus2DateString}T04:00:00.000Z`, @@ -1215,15 +1235,8 @@ async function addBookings(bookings: InputBooking[], eventTypes: InputEventType[ // @ts-ignore statusIn.includes(booking.status) && booking.userId === where.OR[0].userId; - // ~~ SECOND CONDITION checks whether this user is a host of this Event Type - // and that booking.status is a match for the returned query - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - let secondConditionMatches = where.OR[1].eventTypeId.in.includes(booking.eventTypeId); - secondConditionMatches = secondConditionMatches && statusIn.includes(booking.status); - // We return this booking if either condition is met - return firstConditionMatches || secondConditionMatches; + return firstConditionMatches; }) .map((booking) => ({ uid: uuidv4(), @@ -1236,11 +1249,17 @@ async function addBookings(bookings: InputBooking[], eventTypes: InputEventType[ }); } -function addHosts(hosts: InputHost[]) { - prismaMock.host.findMany.mockResolvedValue(hosts); -} - function addUsers(users: InputUser[]) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + prismaMock.user.findUniqueOrThrow.mockImplementation((findUniqueArgs) => { + return new Promise((resolve) => { + resolve({ + email: `IntegrationTestUser${findUniqueArgs?.where.id}@example.com`, + } as unknown as PrismaUser); + }); + }); + prismaMock.user.findMany.mockResolvedValue( users.map((user) => { return { @@ -1271,8 +1290,6 @@ function createBookingScenario(data: ScenarioData) { addUsers(data.users); - addHosts(data.hosts); - const eventType = addEventTypes(data.eventTypes, data.users); if (data.apps) { prismaMock.app.findMany.mockResolvedValue(data.apps as PrismaApp[]); diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index fe609bf4c4..5aad4eac59 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -1,4 +1,5 @@ -import { BookingStatus, Credential } from "@prisma/client"; +import type { Credential } from "@prisma/client"; +import { BookingStatus } from "@prisma/client"; import { getBusyCalendarTimes } from "@calcom/core/CalendarManager"; import dayjs from "@calcom/dayjs"; @@ -34,16 +35,23 @@ export async function getBusyTimes(params: { status: BookingStatus.ACCEPTED, })}` ); + // get user email for attendee checking. + const user = await prisma.user.findUniqueOrThrow({ + where: { + id: userId, + }, + select: { + email: true, + }, + }); /** * A user is considered busy within a given time period if there - * is a booking they own OR host. + * is a booking they own OR attend. * - * Therefore this query does the following: - * - Performs a query for all EventType id's where this user is a host - * - Performs a query for all bookings where: + * Performs a query for all bookings where: * - The given booking is owned by this user, or.. - * - The given booking's EventType is hosted by this user + * - The current user has a different booking at this time he/she attends * * See further discussion within this GH issue: * https://github.com/calcom/cal.com/issues/6374 @@ -52,79 +60,61 @@ export async function getBusyTimes(params: { * logic within getSchedule.test.ts:addBookings */ performance.mark("prismaBookingGetStart"); - const busyTimes: EventBusyDetails[] = - // Getting all EventTypes ID's hosted by this user - await prisma.host - .findMany({ - where: { - userId: { - equals: userId, + + const sharedQuery = { + startTime: { gte: new Date(startTime) }, + endTime: { lte: new Date(endTime) }, + status: { + in: [BookingStatus.ACCEPTED], + }, + }; + // Find bookings that block this user from hosting further bookings. + const busyTimes: EventBusyDetails[] = await prisma.booking + .findMany({ + where: { + OR: [ + // User is primary host (individual events, or primary organizer) + { + ...sharedQuery, + userId, }, - }, - select: { - eventTypeId: true, - }, - }) - - // Converting the response object into an array - .then((thisUserHostedEvents) => thisUserHostedEvents.map((e) => e.eventTypeId)) - - // Finding all bookings owned OR hosted by this user - .then((thisUserHostedEventIds) => { - // This gets applied to both conditions - const sharedQuery = { - startTime: { gte: new Date(startTime) }, - endTime: { lte: new Date(endTime) }, - status: { - in: [BookingStatus.ACCEPTED], - }, - }; - - return prisma.booking.findMany({ - where: { - OR: [ - // Bookings owned by this user - { - ...sharedQuery, - userId, - }, - - // Bookings with an EventType ID that's hosted by this user - { - ...sharedQuery, - eventTypeId: { - in: thisUserHostedEventIds, - }, - }, - ], - }, - select: { - id: true, - startTime: true, - endTime: true, - title: true, - eventType: { - select: { - id: true, - afterEventBuffer: true, - beforeEventBuffer: true, + // The current user has a different booking at this time he/she attends + { + ...sharedQuery, + attendees: { + some: { + email: user.email, }, }, }, - }); - }) - .then((bookings) => - bookings.map(({ startTime, endTime, title, id, eventType }) => ({ - start: dayjs(startTime) - .subtract((eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0), "minute") - .toDate(), - end: dayjs(endTime) - .add((eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0), "minute") - .toDate(), - title, - source: `eventType-${eventType?.id}-booking-${id}`, - })) - ); + ], + }, + select: { + id: true, + startTime: true, + endTime: true, + title: true, + eventType: { + select: { + id: true, + afterEventBuffer: true, + beforeEventBuffer: true, + }, + }, + }, + }) + .then((bookings) => + bookings.map(({ startTime, endTime, title, id, eventType }) => ({ + start: dayjs(startTime) + .subtract((eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0), "minute") + .toDate(), + end: dayjs(endTime) + .add((eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0), "minute") + .toDate(), + title, + source: `eventType-${eventType?.id}-booking-${id}`, + })) + ); logger.silly(`Busy Time from Cal Bookings ${JSON.stringify(busyTimes)}`); performance.mark("prismaBookingGetEnd"); performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");