diff --git a/packages/features/bookings/lib/conflictChecker/checkForConflicts.ts b/packages/features/bookings/lib/conflictChecker/checkForConflicts.ts new file mode 100644 index 0000000000..5311ef317a --- /dev/null +++ b/packages/features/bookings/lib/conflictChecker/checkForConflicts.ts @@ -0,0 +1,45 @@ +import dayjs from "@calcom/dayjs"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; + +type BufferedBusyTimes = BufferedBusyTime[]; +const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); + +// if true, there are conflicts. +export function checkForConflicts(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, length: number) { + // Early return + if (!Array.isArray(busyTimes) || busyTimes.length < 1) { + return false; // guaranteed no conflicts when there is no busy times. + } + + for (const busyTime of busyTimes) { + const startTime = dayjs(busyTime.start); + const endTime = dayjs(busyTime.end); + // Check if time is between start and end times + if (dayjs(time).isBetween(startTime, endTime, null, "[)")) { + log.error( + `NAUF: start between a busy time slot ${safeStringify({ + ...busyTime, + time: dayjs(time).format(), + })}` + ); + return true; + } + // Check if slot end time is between start and end time + if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) { + log.error( + `NAUF: Ends between a busy time slot ${safeStringify({ + ...busyTime, + time: dayjs(time).add(length, "minutes").format(), + })}` + ); + return true; + } + // Check if startTime is between slot + if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) { + return true; + } + } + return false; +} diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts new file mode 100644 index 0000000000..1f1b7cab6f --- /dev/null +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts @@ -0,0 +1,77 @@ +import type { Prisma } from "@prisma/client"; + +import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking"; +import { UserRepository } from "@calcom/lib/server/repository/user"; +import type { userSelect } from "@calcom/prisma"; +import prisma from "@calcom/prisma"; +import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import type { CredentialPayload } from "@calcom/types/Credential"; + +type User = Prisma.UserGetPayload; + +/** + * Gets credentials from the user, team, and org if applicable + * + */ +export const getAllCredentials = async ( + user: User & { credentials: CredentialPayload[] }, + eventType: Awaited> +) => { + const allCredentials = user.credentials; + + // If it's a team event type query for team credentials + if (eventType.team?.id) { + const teamCredentialsQuery = await prisma.credential.findMany({ + where: { + teamId: eventType.team.id, + }, + select: credentialForCalendarServiceSelect, + }); + allCredentials.push(...teamCredentialsQuery); + } + + // If it's a managed event type, query for the parent team's credentials + if (eventType.parentId) { + const teamCredentialsQuery = await prisma.team.findFirst({ + where: { + eventTypes: { + some: { + id: eventType.parentId, + }, + }, + }, + select: { + credentials: { + select: credentialForCalendarServiceSelect, + }, + }, + }); + if (teamCredentialsQuery?.credentials) { + allCredentials.push(...teamCredentialsQuery?.credentials); + } + } + + const { profile } = await UserRepository.enrichUserWithItsProfile({ + user: user, + }); + + // If the user is a part of an organization, query for the organization's credentials + if (profile?.organizationId) { + const org = await prisma.team.findUnique({ + where: { + id: profile.organizationId, + }, + select: { + credentials: { + select: credentialForCalendarServiceSelect, + }, + }, + }); + + if (org?.credentials) { + allCredentials.push(...org.credentials); + } + } + + return allCredentials; +}; diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredential.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredential.ts new file mode 100644 index 0000000000..effe471cbe --- /dev/null +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredential.ts @@ -0,0 +1,23 @@ +import prisma from "@calcom/prisma"; +import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import type { CredentialPayload } from "@calcom/types/Credential"; + +/** + * Refreshes a Credential with fresh data from the database. + * + * @param credential + */ +export async function refreshCredential(credential: CredentialPayload): Promise { + const newCredential = await prisma.credential.findUnique({ + where: { + id: credential.id, + }, + select: credentialForCalendarServiceSelect, + }); + + if (!newCredential) { + return credential; + } else { + return newCredential; + } +} diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts new file mode 100644 index 0000000000..857992b8ad --- /dev/null +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts @@ -0,0 +1,16 @@ +import async from "async"; + +import type { CredentialPayload } from "@calcom/types/Credential"; + +import { refreshCredential } from "./refreshCredential"; + +/** + * Refreshes the given set of credentials. + * + * @param credentials + */ +export async function refreshCredentials( + credentials: Array +): Promise> { + return await async.mapLimit(credentials, 5, refreshCredential); +} diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 6fb80a1167..f717182c6d 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,6 +1,5 @@ import type { App, DestinationCalendar, EventTypeCustomInput } from "@prisma/client"; import { Prisma } from "@prisma/client"; -import async from "async"; import type { IncomingMessage } from "http"; import { isValidPhoneNumber } from "libphonenumber-js"; // eslint-disable-next-line no-restricted-imports @@ -85,7 +84,6 @@ import { EventTypeMetaDataSchema, userMetadata as userMetadataSchema, } from "@calcom/prisma/zod-utils"; -import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; import type { AdditionalInformation, AppsStatus, @@ -97,6 +95,9 @@ import type { CredentialPayload } from "@calcom/types/Credential"; import type { EventResult, PartialReference } from "@calcom/types/EventManager"; import type { EventTypeInfo } from "../../webhooks/lib/sendPayload"; +import { checkForConflicts } from "./conflictChecker/checkForConflicts"; +import { getAllCredentials } from "./getAllCredentialsForUsersOnEvent/getAllCredentials"; +import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCredentials"; import getBookingDataSchema from "./getBookingDataSchema"; import handleSeats from "./handleSeats/handleSeats"; import type { BookingSeat } from "./handleSeats/types"; @@ -105,7 +106,6 @@ const translator = short(); const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); type User = Prisma.UserGetPayload; -type BufferedBusyTimes = BufferedBusyTime[]; type BookingType = Prisma.PromiseReturnType; export type Booking = Prisma.PromiseReturnType; export type NewBookingEventType = @@ -153,142 +153,6 @@ export interface IEventTypePaymentCredentialType { key: Prisma.JsonValue; } -/** - * Refreshes a Credential with fresh data from the database. - * - * @param credential - */ -async function refreshCredential(credential: CredentialPayload): Promise { - const newCredential = await prisma.credential.findUnique({ - where: { - id: credential.id, - }, - select: credentialForCalendarServiceSelect, - }); - - if (!newCredential) { - return credential; - } else { - return newCredential; - } -} - -/** - * Refreshes the given set of credentials. - * - * @param credentials - */ -export async function refreshCredentials( - credentials: Array -): Promise> { - return await async.mapLimit(credentials, 5, refreshCredential); -} - -/** - * Gets credentials from the user, team, and org if applicable - * - */ -const getAllCredentials = async ( - user: User & { credentials: CredentialPayload[] }, - eventType: Awaited> -) => { - const allCredentials = user.credentials; - - // If it's a team event type query for team credentials - if (eventType.team?.id) { - const teamCredentialsQuery = await prisma.credential.findMany({ - where: { - teamId: eventType.team.id, - }, - select: credentialForCalendarServiceSelect, - }); - allCredentials.push(...teamCredentialsQuery); - } - - // If it's a managed event type, query for the parent team's credentials - if (eventType.parentId) { - const teamCredentialsQuery = await prisma.team.findFirst({ - where: { - eventTypes: { - some: { - id: eventType.parentId, - }, - }, - }, - select: { - credentials: { - select: credentialForCalendarServiceSelect, - }, - }, - }); - if (teamCredentialsQuery?.credentials) { - allCredentials.push(...teamCredentialsQuery?.credentials); - } - } - - const { profile } = await UserRepository.enrichUserWithItsProfile({ - user: user, - }); - - // If the user is a part of an organization, query for the organization's credentials - if (profile?.organizationId) { - const org = await prisma.team.findUnique({ - where: { - id: profile.organizationId, - }, - select: { - credentials: { - select: credentialForCalendarServiceSelect, - }, - }, - }); - - if (org?.credentials) { - allCredentials.push(...org.credentials); - } - } - - return allCredentials; -}; - -// if true, there are conflicts. -function checkForConflicts(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, length: number) { - // Early return - if (!Array.isArray(busyTimes) || busyTimes.length < 1) { - return false; // guaranteed no conflicts when there is no busy times. - } - - for (const busyTime of busyTimes) { - const startTime = dayjs(busyTime.start); - const endTime = dayjs(busyTime.end); - // Check if time is between start and end times - if (dayjs(time).isBetween(startTime, endTime, null, "[)")) { - log.error( - `NAUF: start between a busy time slot ${safeStringify({ - ...busyTime, - time: dayjs(time).format(), - })}` - ); - return true; - } - // Check if slot end time is between start and end time - if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) { - log.error( - `NAUF: Ends between a busy time slot ${safeStringify({ - ...busyTime, - time: dayjs(time).add(length, "minutes").format(), - })}` - ); - return true; - } - // Check if startTime is between slot - if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) { - return true; - } - } - return false; -} - export const getEventTypesFromDB = async (eventTypeId: number) => { const eventType = await prisma.eventType.findUniqueOrThrow({ where: { diff --git a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts index 39ab495e16..be6ad6da05 100644 --- a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts +++ b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts @@ -4,6 +4,7 @@ import { uuid } from "short-uuid"; import EventManager from "@calcom/core/EventManager"; import { sendScheduledSeatsEmails } from "@calcom/emails"; +import { refreshCredentials } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials"; import { allowDisablingAttendeeConfirmationEmails, allowDisablingHostConfirmationEmails, @@ -15,7 +16,7 @@ import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import type { IEventTypePaymentCredentialType } from "../../handleNewBooking"; -import { refreshCredentials, findBookingQuery } from "../../handleNewBooking"; +import { findBookingQuery } from "../../handleNewBooking"; import type { SeatedBooking, NewSeatedBookingObject, HandleSeatsResultBooking } from "../types"; const createNewSeat = async ( diff --git a/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts index 520dd8be29..50913ff383 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts @@ -1,12 +1,12 @@ // eslint-disable-next-line no-restricted-imports import EventManager from "@calcom/core/EventManager"; import dayjs from "@calcom/dayjs"; +import { refreshCredentials } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import type { Person } from "@calcom/types/Calendar"; -import { refreshCredentials } from "../../handleNewBooking"; import type { createLoggerWithEventDetails } from "../../handleNewBooking"; import type { HandleSeatsResultBooking,