diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 12526877e5d52..0245e11ea799b 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 { isValidPhoneNumber } from "libphonenumber-js"; // eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import type { NextApiRequest } from "next"; @@ -8,11 +7,10 @@ import type { TFunction } from "next-i18next"; import short, { uuid } from "short-uuid"; import type { Logger } from "tslog"; import { v5 as uuidv5 } from "uuid"; -import z from "zod"; +import type z from "zod"; import processExternalId from "@calcom/app-store/_utils/calendars/processExternalId"; import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata"; -import type { LocationObject } from "@calcom/app-store/locations"; import { MeetLocationType, OrganizerDefaultConferencingAppType, @@ -55,12 +53,7 @@ import { deleteWebhookScheduledTriggers, scheduleTrigger, } from "@calcom/features/webhooks/lib/scheduleTrigger"; -import { - isPrismaObjOrUndefined, - parseBookingLimit, - parseDurationLimit, - parseRecurringEvent, -} from "@calcom/lib"; +import { isPrismaObjOrUndefined, parseBookingLimit, parseDurationLimit } from "@calcom/lib"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { getUTCOffsetByTimezone } from "@calcom/lib/date-fns"; import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents"; @@ -87,9 +80,7 @@ import type { BookingReference } from "@calcom/prisma/client"; import { BookingStatus, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import { - EventTypeMetaDataSchema, bookingCreateSchemaLegacyPropsForApi, - customInputSchema, userMetadata as userMetadataSchema, } from "@calcom/prisma/zod-utils"; import type { @@ -107,6 +98,12 @@ import { checkForConflicts } from "./conflictChecker/checkForConflicts"; import { getAllCredentials } from "./getAllCredentialsForUsersOnEvent/getAllCredentials"; import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCredentials"; import getBookingDataSchema from "./getBookingDataSchema"; +import { checkIfBookerEmailIsBlocked } from "./handleNewBooking/checkIfBookerEmailIsBlocked"; +import { getEventTypesFromDB } from "./handleNewBooking/getEventTypesFromDB"; +import type { getEventTypeResponse } from "./handleNewBooking/getEventTypesFromDB"; +import { getRequiresConfirmationFlags } from "./handleNewBooking/getRequiresConfirmationFlags"; +import { handleAppsStatus } from "./handleNewBooking/handleAppsStatus"; +import { handleCustomInputs } from "./handleNewBooking/handleCustomInputs"; import { loadUsers } from "./handleNewBooking/loadUsers"; import handleSeats from "./handleSeats/handleSeats"; import type { BookingSeat } from "./handleSeats/types"; @@ -117,9 +114,7 @@ const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); type User = Prisma.UserGetPayload; type BookingType = Prisma.PromiseReturnType; export type Booking = Prisma.PromiseReturnType; -export type NewBookingEventType = - | Awaited> - | Awaited>; +export type NewBookingEventType = Awaited> | getEventTypeResponse; // Work with Typescript to require reqBody.end type ReqBodyWithoutEnd = z.infer>; @@ -162,127 +157,6 @@ export interface IEventTypePaymentCredentialType { key: Prisma.JsonValue; } -export const getEventTypesFromDB = async (eventTypeId: number) => { - const eventType = await prisma.eventType.findUniqueOrThrow({ - where: { - id: eventTypeId, - }, - select: { - id: true, - customInputs: true, - disableGuests: true, - users: { - select: { - credentials: { - select: credentialForCalendarServiceSelect, - }, - ...userSelect.select, - }, - }, - slug: true, - team: { - select: { - id: true, - name: true, - parentId: true, - }, - }, - bookingFields: true, - title: true, - length: true, - eventName: true, - schedulingType: true, - description: true, - periodType: true, - periodStartDate: true, - periodEndDate: true, - periodDays: true, - periodCountCalendarDays: true, - lockTimeZoneToggleOnBookingPage: true, - requiresConfirmation: true, - requiresBookerEmailVerification: true, - minimumBookingNotice: true, - userId: true, - price: true, - currency: true, - metadata: true, - destinationCalendar: true, - hideCalendarNotes: true, - seatsPerTimeSlot: true, - recurringEvent: true, - seatsShowAttendees: true, - seatsShowAvailabilityCount: true, - bookingLimits: true, - durationLimits: true, - assignAllTeamMembers: true, - parentId: true, - useEventTypeDestinationCalendarEmail: true, - owner: { - select: { - hideBranding: true, - }, - }, - workflows: { - include: { - workflow: { - include: { - steps: true, - }, - }, - }, - }, - locations: true, - timeZone: true, - schedule: { - select: { - id: true, - availability: true, - timeZone: true, - }, - }, - hosts: { - select: { - isFixed: true, - priority: true, - user: { - select: { - credentials: { - select: credentialForCalendarServiceSelect, - }, - ...userSelect.select, - }, - }, - }, - }, - availability: { - select: { - date: true, - startTime: true, - endTime: true, - days: true, - }, - }, - secondaryEmailId: true, - secondaryEmail: { - select: { - id: true, - email: true, - }, - }, - }, - }); - - return { - ...eventType, - metadata: EventTypeMetaDataSchema.parse(eventType?.metadata || {}), - recurringEvent: parseRecurringEvent(eventType?.recurringEvent), - customInputs: customInputSchema.array().parse(eventType?.customInputs || []), - locations: (eventType?.locations ?? []) as LocationObject[], - bookingFields: getBookingFieldsWithSystemFields(eventType || {}), - isDynamic: false, - }; -}; - type IsFixedAwareUser = User & { isFixed: boolean; credentials: CredentialPayload[]; @@ -291,7 +165,7 @@ type IsFixedAwareUser = User & { }; export async function ensureAvailableUsers( - eventType: Awaited> & { + eventType: getEventTypeResponse & { users: IsFixedAwareUser[]; }, input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType }, @@ -479,7 +353,7 @@ export async function getBookingData({ schema, }: { req: NextApiRequest; - eventType: Awaited>; + eventType: getEventTypeResponse; schema: T; }) { const reqBody = await schema.parseAsync(req.body); @@ -722,7 +596,7 @@ export function getCustomInputsResponses( responses?: Record; customInputs?: z.infer["customInputs"]; }, - eventTypeCustomInputs: Awaited>["customInputs"] + eventTypeCustomInputs: getEventTypeResponse["customInputs"] ) { const customInputsResponses = {} as NonNullable; if (reqBody.customInputs && (reqBody.customInputs.length || 0) > 0) { @@ -777,43 +651,6 @@ export const createLoggerWithEventDetails = ( }); }; -export function handleAppsStatus( - results: EventResult[], - booking: (Booking & { appsStatus?: AppsStatus[] }) | null, - reqAppsStatus: ReqAppsStatus -) { - // Taking care of apps status - const resultStatus: AppsStatus[] = results.map((app) => ({ - appName: app.appName, - type: app.type, - success: app.success ? 1 : 0, - failures: !app.success ? 1 : 0, - errors: app.calError ? [app.calError] : [], - warnings: app.calWarnings, - })); - - if (reqAppsStatus === undefined) { - if (booking !== null) { - booking.appsStatus = resultStatus; - } - return resultStatus; - } - // From down here we can assume reqAppsStatus is not undefined anymore - // Other status exist, so this is the last booking of a series, - // proceeding to prepare the info for the event - const calcAppsStatus = reqAppsStatus.concat(resultStatus).reduce((prev, curr) => { - if (prev[curr.type]) { - prev[curr.type].success += curr.success; - prev[curr.type].errors = prev[curr.type].errors.concat(curr.errors); - prev[curr.type].warnings = prev[curr.type].warnings?.concat(curr.warnings || []); - } else { - prev[curr.type] = curr; - } - return prev; - }, {} as { [key: string]: AppsStatus }); - return Object.values(calcAppsStatus); -} - function getICalSequence(originalRescheduledBooking: BookingType | null) { // If new booking set the sequence to 0 if (!originalRescheduledBooking) { @@ -880,65 +717,6 @@ type BookingDataSchemaGetter = | typeof getBookingDataSchema | typeof import("@calcom/features/bookings/lib/getBookingDataSchemaForApi").default; -const checkIfBookerEmailIsBlocked = async ({ - bookerEmail, - loggedInUserId, -}: { - bookerEmail: string; - loggedInUserId?: number; -}) => { - const baseEmail = extractBaseEmail(bookerEmail); - const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS - ? process.env.BLACKLISTED_GUEST_EMAILS.split(",") - : []; - - const blacklistedEmail = blacklistedGuestEmails.find( - (guestEmail: string) => guestEmail.toLowerCase() === baseEmail.toLowerCase() - ); - - if (!blacklistedEmail) { - return false; - } - - const user = await prisma.user.findFirst({ - where: { - OR: [ - { - email: baseEmail, - emailVerified: { - not: null, - }, - }, - { - secondaryEmails: { - some: { - email: baseEmail, - emailVerified: { - not: null, - }, - }, - }, - }, - ], - }, - select: { - id: true, - email: true, - }, - }); - - if (!user) { - throw new HttpError({ statusCode: 403, message: "Cannot use this email to create the booking." }); - } - - if (user.id !== loggedInUserId) { - throw new HttpError({ - statusCode: 403, - message: `Attendee email has been blocked. Make sure to login as ${bookerEmail} to use this email for creating a booking.`, - }); - } -}; - async function handler( req: NextApiRequest & { userId?: number | undefined; @@ -1245,7 +1023,7 @@ async function handler( //checks what users are available if (isFirstSeat) { - const eventTypeWithUsers: Awaited> & { + const eventTypeWithUsers: getEventTypeResponse & { users: IsFixedAwareUser[]; } = { ...eventType, @@ -2540,77 +2318,3 @@ function getVideoCallDetails({ return { videoCallUrl, metadata, updatedVideoEvent }; } - -function getRequiresConfirmationFlags({ - eventType, - bookingStartTime, - userId, - paymentAppData, - originalRescheduledBookingOrganizerId, -}: { - eventType: Pick>, "metadata" | "requiresConfirmation">; - bookingStartTime: string; - userId: number | undefined; - paymentAppData: { price: number }; - originalRescheduledBookingOrganizerId: number | undefined; -}) { - let requiresConfirmation = eventType?.requiresConfirmation; - const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold; - if (rcThreshold) { - if (dayjs(dayjs(bookingStartTime).utc().format()).diff(dayjs(), rcThreshold.unit) > rcThreshold.time) { - requiresConfirmation = false; - } - } - - // If the user is not the owner of the event, new booking should be always pending. - // Otherwise, an owner rescheduling should be always accepted. - // Before comparing make sure that userId is set, otherwise undefined === undefined - const userReschedulingIsOwner = !!(userId && originalRescheduledBookingOrganizerId === userId); - const isConfirmedByDefault = (!requiresConfirmation && !paymentAppData.price) || userReschedulingIsOwner; - return { - /** - * Organizer of the booking is rescheduling - */ - userReschedulingIsOwner, - /** - * Booking won't need confirmation to be ACCEPTED - */ - isConfirmedByDefault, - }; -} - -function handleCustomInputs( - eventTypeCustomInputs: EventTypeCustomInput[], - reqCustomInputs: { - value: string | boolean; - label: string; - }[] -) { - eventTypeCustomInputs.forEach((etcInput) => { - if (etcInput.required) { - const input = reqCustomInputs.find((i) => i.label === etcInput.label); - if (etcInput.type === "BOOL") { - z.literal(true, { - errorMap: () => ({ message: `Missing ${etcInput.type} customInput: '${etcInput.label}'` }), - }).parse(input?.value); - } else if (etcInput.type === "PHONE") { - z.string({ - errorMap: () => ({ - message: `Missing ${etcInput.type} customInput: '${etcInput.label}'`, - }), - }) - .refine((val) => isValidPhoneNumber(val), { - message: "Phone number is invalid", - }) - .parse(input?.value); - } else { - // type: NUMBER are also passed as string - z.string({ - errorMap: () => ({ message: `Missing ${etcInput.type} customInput: '${etcInput.label}'` }), - }) - .min(1) - .parse(input?.value); - } - } - }); -} diff --git a/packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts b/packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts new file mode 100644 index 0000000000000..4f4aa17649c48 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts @@ -0,0 +1,62 @@ +import { extractBaseEmail } from "@calcom/lib/extract-base-email"; +import { HttpError } from "@calcom/lib/http-error"; +import prisma from "@calcom/prisma"; + +export const checkIfBookerEmailIsBlocked = async ({ + bookerEmail, + loggedInUserId, +}: { + bookerEmail: string; + loggedInUserId?: number; +}) => { + const baseEmail = extractBaseEmail(bookerEmail); + const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS + ? process.env.BLACKLISTED_GUEST_EMAILS.split(",") + : []; + + const blacklistedEmail = blacklistedGuestEmails.find( + (guestEmail: string) => guestEmail.toLowerCase() === baseEmail.toLowerCase() + ); + + if (!blacklistedEmail) { + return false; + } + + const user = await prisma.user.findFirst({ + where: { + OR: [ + { + email: baseEmail, + emailVerified: { + not: null, + }, + }, + { + secondaryEmails: { + some: { + email: baseEmail, + emailVerified: { + not: null, + }, + }, + }, + }, + ], + }, + select: { + id: true, + email: true, + }, + }); + + if (!user) { + throw new HttpError({ statusCode: 403, message: "Cannot use this email to create the booking." }); + } + + if (user.id !== loggedInUserId) { + throw new HttpError({ + statusCode: 403, + message: `Attendee email has been blocked. Make sure to login as ${bookerEmail} to use this email for creating a booking.`, + }); + } +}; diff --git a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts new file mode 100644 index 0000000000000..8948bcb26840d --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts @@ -0,0 +1,129 @@ +import type { LocationObject } from "@calcom/app-store/locations"; +import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; +import { parseRecurringEvent } from "@calcom/lib"; +import prisma, { userSelect } from "@calcom/prisma"; +import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import { EventTypeMetaDataSchema, customInputSchema } from "@calcom/prisma/zod-utils"; + +export const getEventTypesFromDB = async (eventTypeId: number) => { + const eventType = await prisma.eventType.findUniqueOrThrow({ + where: { + id: eventTypeId, + }, + select: { + id: true, + customInputs: true, + disableGuests: true, + users: { + select: { + credentials: { + select: credentialForCalendarServiceSelect, + }, + ...userSelect.select, + }, + }, + slug: true, + team: { + select: { + id: true, + name: true, + parentId: true, + }, + }, + bookingFields: true, + title: true, + length: true, + eventName: true, + schedulingType: true, + description: true, + periodType: true, + periodStartDate: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + lockTimeZoneToggleOnBookingPage: true, + requiresConfirmation: true, + requiresBookerEmailVerification: true, + minimumBookingNotice: true, + userId: true, + price: true, + currency: true, + metadata: true, + destinationCalendar: true, + hideCalendarNotes: true, + seatsPerTimeSlot: true, + recurringEvent: true, + seatsShowAttendees: true, + seatsShowAvailabilityCount: true, + bookingLimits: true, + durationLimits: true, + assignAllTeamMembers: true, + parentId: true, + useEventTypeDestinationCalendarEmail: true, + owner: { + select: { + hideBranding: true, + }, + }, + workflows: { + include: { + workflow: { + include: { + steps: true, + }, + }, + }, + }, + locations: true, + timeZone: true, + schedule: { + select: { + id: true, + availability: true, + timeZone: true, + }, + }, + hosts: { + select: { + isFixed: true, + priority: true, + user: { + select: { + credentials: { + select: credentialForCalendarServiceSelect, + }, + ...userSelect.select, + }, + }, + }, + }, + availability: { + select: { + date: true, + startTime: true, + endTime: true, + days: true, + }, + }, + secondaryEmailId: true, + secondaryEmail: { + select: { + id: true, + email: true, + }, + }, + }, + }); + + return { + ...eventType, + metadata: EventTypeMetaDataSchema.parse(eventType?.metadata || {}), + recurringEvent: parseRecurringEvent(eventType?.recurringEvent), + customInputs: customInputSchema.array().parse(eventType?.customInputs || []), + locations: (eventType?.locations ?? []) as LocationObject[], + bookingFields: getBookingFieldsWithSystemFields(eventType || {}), + isDynamic: false, + }; +}; + +export type getEventTypeResponse = Awaited>; diff --git a/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts b/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts new file mode 100644 index 0000000000000..23a2df53f956c --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts @@ -0,0 +1,71 @@ +import dayjs from "@calcom/dayjs"; + +import type { getEventTypeResponse } from "./getEventTypesFromDB"; + +type EventType = Pick; +type PaymentAppData = { price: number }; + +export function getRequiresConfirmationFlags({ + eventType, + bookingStartTime, + userId, + paymentAppData, + originalRescheduledBookingOrganizerId, +}: { + eventType: EventType; + bookingStartTime: string; + userId: number | undefined; + paymentAppData: PaymentAppData; + originalRescheduledBookingOrganizerId: number | undefined; +}) { + const requiresConfirmation = determineRequiresConfirmation(eventType, bookingStartTime); + const userReschedulingIsOwner = isUserReschedulingOwner(userId, originalRescheduledBookingOrganizerId); + const isConfirmedByDefault = determineIsConfirmedByDefault( + requiresConfirmation, + paymentAppData.price, + userReschedulingIsOwner + ); + + return { + /** + * Organizer of the booking is rescheduling + */ + userReschedulingIsOwner, + /** + * Booking won't need confirmation to be ACCEPTED + */ + isConfirmedByDefault, + }; +} + +function determineRequiresConfirmation(eventType: EventType, bookingStartTime: string): boolean { + let requiresConfirmation = eventType?.requiresConfirmation; + const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold; + + if (rcThreshold) { + const timeDifference = dayjs(dayjs(bookingStartTime).utc().format()).diff(dayjs(), rcThreshold.unit); + if (timeDifference > rcThreshold.time) { + requiresConfirmation = false; + } + } + + return requiresConfirmation; +} + +function isUserReschedulingOwner( + userId: number | undefined, + originalRescheduledBookingOrganizerId: number | undefined +): boolean { + // If the user is not the owner of the event, new booking should be always pending. + // Otherwise, an owner rescheduling should be always accepted. + // Before comparing make sure that userId is set, otherwise undefined === undefined + return !!(userId && originalRescheduledBookingOrganizerId === userId); +} + +function determineIsConfirmedByDefault( + requiresConfirmation: boolean, + price: number, + userReschedulingIsOwner: boolean +): boolean { + return (!requiresConfirmation && price === 0) || userReschedulingIsOwner; +} diff --git a/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts b/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts new file mode 100644 index 0000000000000..29792e43dea11 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts @@ -0,0 +1,60 @@ +import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; +import type { EventResult } from "@calcom/types/EventManager"; + +import type { ReqAppsStatus, Booking } from "../handleNewBooking"; + +export function handleAppsStatus( + results: EventResult[], + booking: (Booking & { appsStatus?: AppsStatus[] }) | null, + reqAppsStatus: ReqAppsStatus +): AppsStatus[] { + const resultStatus = mapResultsToAppsStatus(results); + + if (reqAppsStatus === undefined) { + return updateBookingWithStatus(booking, resultStatus); + } + + return calculateAggregatedAppsStatus(reqAppsStatus, resultStatus); +} + +function mapResultsToAppsStatus(results: EventResult[]): AppsStatus[] { + return results.map((app) => ({ + appName: app.appName, + type: app.type, + success: app.success ? 1 : 0, + failures: !app.success ? 1 : 0, + errors: app.calError ? [app.calError] : [], + warnings: app.calWarnings, + })); +} + +function updateBookingWithStatus( + booking: (Booking & { appsStatus?: AppsStatus[] }) | null, + resultStatus: AppsStatus[] +): AppsStatus[] { + if (booking !== null) { + booking.appsStatus = resultStatus; + } + return resultStatus; +} + +function calculateAggregatedAppsStatus( + reqAppsStatus: NonNullable, + resultStatus: AppsStatus[] +): AppsStatus[] { + // From down here we can assume reqAppsStatus is not undefined anymore + // Other status exist, so this is the last booking of a series, + // proceeding to prepare the info for the event + const aggregatedStatus = reqAppsStatus.concat(resultStatus).reduce((acc, curr) => { + if (acc[curr.type]) { + acc[curr.type].success += curr.success; + acc[curr.type].errors = acc[curr.type].errors.concat(curr.errors); + acc[curr.type].warnings = acc[curr.type].warnings?.concat(curr.warnings || []); + } else { + acc[curr.type] = curr; + } + return acc; + }, {} as { [key: string]: AppsStatus }); + + return Object.values(aggregatedStatus); +} diff --git a/packages/features/bookings/lib/handleNewBooking/handleCustomInputs.ts b/packages/features/bookings/lib/handleNewBooking/handleCustomInputs.ts new file mode 100644 index 0000000000000..be6058a94e62c --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/handleCustomInputs.ts @@ -0,0 +1,56 @@ +import type { EventTypeCustomInput } from "@prisma/client"; +import { isValidPhoneNumber } from "libphonenumber-js"; +import z from "zod"; + +type CustomInput = { + value: string | boolean; + label: string; +}; + +export function handleCustomInputs( + eventTypeCustomInputs: EventTypeCustomInput[], + reqCustomInputs: CustomInput[] +) { + eventTypeCustomInputs.forEach((etcInput) => { + if (etcInput.required) { + const input = reqCustomInputs.find((input) => input.label === etcInput.label); + validateInput(etcInput, input?.value); + } + }); +} + +function validateInput(etcInput: EventTypeCustomInput, value: string | boolean | undefined) { + const errorMessage = `Missing ${etcInput.type} customInput: '${etcInput.label}'`; + + if (etcInput.type === "BOOL") { + validateBooleanInput(value, errorMessage); + } else if (etcInput.type === "PHONE") { + validatePhoneInput(value, errorMessage); + } else { + validateStringInput(value, errorMessage); + } +} + +function validateBooleanInput(value: string | boolean | undefined, errorMessage: string) { + z.literal(true, { + errorMap: () => ({ message: errorMessage }), + }).parse(value); +} + +function validatePhoneInput(value: string | boolean | undefined, errorMessage: string) { + z.string({ + errorMap: () => ({ message: errorMessage }), + }) + .refine((val) => isValidPhoneNumber(val), { + message: "Phone number is invalid", + }) + .parse(value); +} + +function validateStringInput(value: string | boolean | undefined, errorMessage: string) { + z.string({ + errorMap: () => ({ message: errorMessage }), + }) + .min(1) + .parse(value); +} diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 0dcd16c605972..9bf8b35bd9dcd 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -6,8 +6,9 @@ import { sendRescheduledEmails } from "@calcom/emails"; import prisma from "@calcom/prisma"; import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; -import { addVideoCallDataToEvent, handleAppsStatus, findBookingQuery } from "../../../handleNewBooking"; +import { addVideoCallDataToEvent, findBookingQuery } from "../../../handleNewBooking"; import type { Booking, createLoggerWithEventDetails } from "../../../handleNewBooking"; +import { handleAppsStatus } from "../../../handleNewBooking/handleAppsStatus"; import type { SeatedBooking, RescheduleSeatedBookingObject } from "../../types"; const moveSeatedBookingToNewTimeSlot = async ( diff --git a/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts index 3512e9f9f1557..0e6eb031e70e1 100644 --- a/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts +++ b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts @@ -1,6 +1,6 @@ import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; -import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking"; +import type { getEventTypeResponse } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; import { scheduleEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import type { BookingInfo } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; import type { getDefaultEvent } from "@calcom/lib/defaultEvents"; @@ -9,9 +9,7 @@ import { WorkflowTriggerEvents, TimeUnit, WorkflowActions, WorkflowTemplates } f const log = logger.getSubLogger({ prefix: ["[scheduleMandatoryReminder]"] }); -export type NewBookingEventType = - | Awaited> - | Awaited>; +export type NewBookingEventType = Awaited> | getEventTypeResponse; export async function scheduleMandatoryReminder( evt: BookingInfo, diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts index 82c4ab4364ffd..523b0e34cb66e 100644 --- a/packages/features/instant-meeting/handleInstantMeeting.ts +++ b/packages/features/instant-meeting/handleInstantMeeting.ts @@ -8,11 +8,8 @@ import { createInstantMeetingWithCalVideo } from "@calcom/core/videoClient"; import dayjs from "@calcom/dayjs"; import getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; -import { - getBookingData, - getCustomInputsResponses, - getEventTypesFromDB, -} from "@calcom/features/bookings/lib/handleNewBooking"; +import { getBookingData, getCustomInputsResponses } from "@calcom/features/bookings/lib/handleNewBooking"; +import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; import { getFullName } from "@calcom/features/form-builder/utils"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined } from "@calcom/lib";