diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index fc3324ede1..c7205a5ee3 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -269,12 +269,14 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + }, }); // As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available @@ -353,13 +355,15 @@ describe("getSchedule", () => { // Day Plus 2 is completely free - It only has non accepted bookings const scheduleOnCompletelyFreeDay = await getSchedule({ - eventTypeId: 1, - // EventTypeSlug doesn't matter for non-dynamic events - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + // EventTypeSlug doesn't matter for non-dynamic events + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); // getSchedule returns timeslots in GMT @@ -384,12 +388,14 @@ describe("getSchedule", () => { // Day plus 3 const scheduleForDayWithOneBooking = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus2DateString}T18:30:00.000Z`, - endTime: `${plus3DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus2DateString}T18:30:00.000Z`, + endTime: `${plus3DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForDayWithOneBooking).toHaveTimeSlots( @@ -448,12 +454,14 @@ describe("getSchedule", () => { const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); const scheduleForEventWith30Length = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventWith30Length).toHaveTimeSlots( @@ -482,12 +490,14 @@ describe("getSchedule", () => { ); const scheduleForEventWith30minsLengthAndSlotInterval2hrs = await getSchedule({ - eventTypeId: 2, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 2, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); // `slotInterval` takes precedence over `length` // 4:30 is utc so it is 10:00 in IST @@ -545,12 +555,14 @@ describe("getSchedule", () => { const { dateString: todayDateString } = getDate(); const { dateString: minus1DateString } = getDate({ dateIncrement: -1 }); const scheduleForEventWithBookingNotice13Hrs = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${minus1DateString}T18:30:00.000Z`, - endTime: `${todayDateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${minus1DateString}T18:30:00.000Z`, + endTime: `${todayDateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventWithBookingNotice13Hrs).toHaveTimeSlots( [ @@ -564,12 +576,14 @@ describe("getSchedule", () => { ); const scheduleForEventWithBookingNotice10Hrs = await getSchedule({ - eventTypeId: 2, - eventTypeSlug: "", - startTime: `${minus1DateString}T18:30:00.000Z`, - endTime: `${todayDateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 2, + eventTypeSlug: "", + startTime: `${minus1DateString}T18:30:00.000Z`, + endTime: `${todayDateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventWithBookingNotice10Hrs).toHaveTimeSlots( [ @@ -627,12 +641,14 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); const scheduleForEventOnADayWithNonCalBooking = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus2DateString}T18:30:00.000Z`, - endTime: `${plus3DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus2DateString}T18:30:00.000Z`, + endTime: `${plus3DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventOnADayWithNonCalBooking).toHaveTimeSlots( @@ -700,12 +716,14 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); const scheduleForEventOnADayWithCalBooking = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventOnADayWithCalBooking).toHaveTimeSlots( @@ -757,12 +775,14 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); const schedule = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(schedule).toHaveTimeSlots( @@ -820,12 +840,14 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); const scheduleForEventOnADayWithDateOverride = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(scheduleForEventOnADayWithDateOverride).toHaveTimeSlots( @@ -905,12 +927,14 @@ describe("getSchedule", () => { // Requesting this user's availability for their // individual Event Type const thisUserAvailability = await getSchedule({ - eventTypeId: 2, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: false, + input: { + eventTypeId: 2, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + } }); expect(thisUserAvailability).toHaveTimeSlots( @@ -1002,12 +1026,14 @@ describe("getSchedule", () => { }); const scheduleForTeamEventOnADayWithNoBooking = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${todayDateString}T18:30:00.000Z`, - endTime: `${plus1DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: true, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${todayDateString}T18:30:00.000Z`, + endTime: `${plus1DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: true, + } }); expect(scheduleForTeamEventOnADayWithNoBooking).toHaveTimeSlots( @@ -1030,12 +1056,14 @@ describe("getSchedule", () => { ); const scheduleForTeamEventOnADayWithOneBookingForEachUser = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: true, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: true, + } }); // A user with blocked time in another event, still affects Team Event availability @@ -1137,12 +1165,14 @@ describe("getSchedule", () => { hosts: [], }); const scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus1DateString}T18:30:00.000Z`, - endTime: `${plus2DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: true, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: true, + } }); // A user with blocked time in another event, still affects Team Event availability expect(scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots).toHaveTimeSlots( @@ -1163,12 +1193,14 @@ describe("getSchedule", () => { ); const scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot = await getSchedule({ - eventTypeId: 1, - eventTypeSlug: "", - startTime: `${plus2DateString}T18:30:00.000Z`, - endTime: `${plus3DateString}T18:29:59.999Z`, - timeZone: Timezones["+5:30"], - isTeamEvent: true, + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus2DateString}T18:30:00.000Z`, + endTime: `${plus3DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: true, + } }); // A user with blocked time in another event, still affects Team Event availability expect(scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot).toHaveTimeSlots( diff --git a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts index 91ada28bdd..149fd46ad3 100644 --- a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts @@ -1,11 +1,17 @@ +import type { IncomingMessage } from "http"; + import type { TGetScheduleInputSchema } from "./getSchedule.schema"; import { getAvailableSlots } from "./util"; -type GetScheduleOptions = { - ctx: Record; +export type GetScheduleOptions = { + ctx?: ContextForGetSchedule; input: TGetScheduleInputSchema; }; -export const getScheduleHandler = async ({ input }: GetScheduleOptions) => { - return await getAvailableSlots(input); +interface ContextForGetSchedule extends Record { + req?: (IncomingMessage & { cookies: Partial<{ [key: string]: string }> }) | undefined; +} + +export const getScheduleHandler = async ({ ctx, input }: GetScheduleOptions) => { + return await getAvailableSlots({ ctx, input }); }; diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 25986fe71e..4db310ef62 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -7,6 +7,7 @@ import type { CurrentSeats } from "@calcom/core/getUserAvailability"; import { getUserAvailability } from "@calcom/core/getUserAvailability"; import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; +import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; import isTimeOutOfBounds from "@calcom/lib/isOutOfBounds"; import logger from "@calcom/lib/logger"; @@ -19,6 +20,7 @@ import type { EventBusyDate } from "@calcom/types/Calendar"; import { TRPCError } from "@trpc/server"; +import type { GetScheduleOptions } from "./getSchedule.handler"; import type { TGetScheduleInputSchema } from "./getSchedule.schema"; export const checkIfIsAvailable = ({ @@ -74,19 +76,27 @@ async function getEventTypeId({ slug, eventTypeSlug, isTeamEvent, + organizationDetails, }: { slug?: string; eventTypeSlug?: string; isTeamEvent: boolean; + organizationDetails?: { currentOrgDomain: string | null; isValidOrgDomain: boolean }; }) { if (!eventTypeSlug || !slug) return null; let teamId; let userId; if (isTeamEvent) { - teamId = await getTeamIdFromSlug(slug); + teamId = await getTeamIdFromSlug( + slug, + organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false } + ); } else { - userId = await getUserIdFromUsername(slug); + userId = await getUserIdFromUsername( + slug, + organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false } + ); } const eventType = await prisma.eventType.findFirst({ where: { @@ -104,7 +114,10 @@ async function getEventTypeId({ return eventType?.id; } -export async function getEventType(input: TGetScheduleInputSchema) { +export async function getEventType( + input: TGetScheduleInputSchema, + organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean } +) { const { eventTypeSlug, usernameList, isTeamEvent } = input; const eventTypeId = input.eventTypeId || @@ -113,6 +126,7 @@ export async function getEventType(input: TGetScheduleInputSchema) { slug: usernameList?.[0], eventTypeSlug: eventTypeSlug, isTeamEvent, + organizationDetails, })); if (!eventTypeId) { @@ -223,12 +237,17 @@ export async function getDynamicEventType(input: TGetScheduleInputSchema) { }); } -export function getRegularOrDynamicEventType(input: TGetScheduleInputSchema) { +export function getRegularOrDynamicEventType( + input: TGetScheduleInputSchema, + organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean } +) { const isDynamicBooking = input.usernameList && input.usernameList.length > 1; - return isDynamicBooking ? getDynamicEventType(input) : getEventType(input); + return isDynamicBooking ? getDynamicEventType(input) : getEventType(input, organizationDetails); } -export async function getAvailableSlots(input: TGetScheduleInputSchema) { +export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) { + const orgDetails = orgDomainConfig(ctx?.req?.headers.host ?? ""); + if (input.debug === true) { logger.setSettings({ minLevel: "debug" }); } @@ -236,7 +255,7 @@ export async function getAvailableSlots(input: TGetScheduleInputSchema) { logger.setSettings({ minLevel: "silly" }); } const startPrismaEventTypeGet = performance.now(); - const eventType = await getRegularOrDynamicEventType(input); + const eventType = await getRegularOrDynamicEventType(input, orgDetails); const endPrismaEventTypeGet = performance.now(); logger.debug( `Prisma eventType get took ${endPrismaEventTypeGet - startPrismaEventTypeGet}ms for event:${ @@ -492,10 +511,15 @@ export async function getAvailableSlots(input: TGetScheduleInputSchema) { }; } -async function getUserIdFromUsername(username: string) { +async function getUserIdFromUsername( + username: string, + organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean } +) { + const { currentOrgDomain, isValidOrgDomain } = organizationDetails; const user = await prisma.user.findFirst({ where: { username, + organization: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null, }, select: { id: true, @@ -504,10 +528,15 @@ async function getUserIdFromUsername(username: string) { return user?.id; } -async function getTeamIdFromSlug(slug: string) { +async function getTeamIdFromSlug( + slug: string, + organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean } +) { + const { currentOrgDomain, isValidOrgDomain } = organizationDetails; const team = await prisma.team.findFirst({ where: { slug, + parent: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null, }, select: { id: true,