| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  | import { | 
					
						
							|  |  |  |   BookingStatus, | 
					
						
							|  |  |  |   Credential, | 
					
						
							|  |  |  |   Payment, | 
					
						
							|  |  |  |   Prisma, | 
					
						
							|  |  |  |   SchedulingType, | 
					
						
							|  |  |  |   WebhookTriggerEvents, | 
					
						
							|  |  |  | } from "@prisma/client"; | 
					
						
							| 
									
										
										
										
											2021-09-23 09:35:39 +00:00
										 |  |  | import async from "async"; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | import dayjs from "dayjs"; | 
					
						
							| 
									
										
										
										
											2021-10-18 21:07:06 +00:00
										 |  |  | import dayjsBusinessTime from "dayjs-business-time"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | import isBetween from "dayjs/plugin/isBetween"; | 
					
						
							|  |  |  | import timezone from "dayjs/plugin/timezone"; | 
					
						
							|  |  |  | import utc from "dayjs/plugin/utc"; | 
					
						
							|  |  |  | import type { NextApiRequest, NextApiResponse } from "next"; | 
					
						
							|  |  |  | import short from "short-uuid"; | 
					
						
							|  |  |  | import { v5 as uuidv5 } from "uuid"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 22:00:30 +00:00
										 |  |  | import { getBusyCalendarTimes } from "@calcom/core/CalendarManager"; | 
					
						
							|  |  |  | import EventManager from "@calcom/core/EventManager"; | 
					
						
							|  |  |  | import { getBusyVideoTimes } from "@calcom/core/videoClient"; | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  | import { getDefaultEvent, getUsernameList, getGroupName } from "@calcom/lib/defaultEvents"; | 
					
						
							| 
									
										
										
										
											2022-03-16 23:36:43 +00:00
										 |  |  | import { getErrorFromUnknown } from "@calcom/lib/errors"; | 
					
						
							| 
									
										
										
										
											2022-03-23 22:00:30 +00:00
										 |  |  | import logger from "@calcom/lib/logger"; | 
					
						
							|  |  |  | import notEmpty from "@calcom/lib/notEmpty"; | 
					
						
							|  |  |  | import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  | import type { AdditionInformation, CalendarEvent, EventBusyDate, Person } from "@calcom/types/Calendar"; | 
					
						
							| 
									
										
										
										
											2022-03-23 22:00:30 +00:00
										 |  |  | import type { EventResult, PartialReference } from "@calcom/types/EventManager"; | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  | import { handlePayment } from "@ee/lib/stripe/server"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2022-03-07 18:18:23 +00:00
										 |  |  |   sendAttendeeRequestEmail, | 
					
						
							| 
									
										
										
										
											2022-03-23 22:00:30 +00:00
										 |  |  |   sendOrganizerRequestEmail, | 
					
						
							|  |  |  |   sendRescheduledEmails, | 
					
						
							|  |  |  |   sendScheduledEmails, | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | } from "@lib/emails/email-manager"; | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  | import { ensureArray } from "@lib/ensureArray"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | import { getEventName } from "@lib/event"; | 
					
						
							|  |  |  | import prisma from "@lib/prisma"; | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  | import { BookingCreateBody } from "@lib/types/booking"; | 
					
						
							| 
									
										
										
										
											2021-10-07 15:14:47 +00:00
										 |  |  | import sendPayload from "@lib/webhooks/sendPayload"; | 
					
						
							| 
									
										
										
										
											2021-11-22 11:37:07 +00:00
										 |  |  | import getSubscribers from "@lib/webhooks/subscriptions"; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  | import { getTranslation } from "@server/lib/i18n"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-02 11:07:13 +00:00
										 |  |  | import verifyAccount from "../../../web3/utils/verifyAccount"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-18 21:07:06 +00:00
										 |  |  | dayjs.extend(dayjsBusinessTime); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | dayjs.extend(utc); | 
					
						
							|  |  |  | dayjs.extend(isBetween); | 
					
						
							|  |  |  | dayjs.extend(timezone); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const translator = short(); | 
					
						
							|  |  |  | const log = logger.getChildLogger({ prefix: ["[api] book:user"] }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | type BufferedBusyTimes = BufferedBusyTime[]; | 
					
						
							| 
									
										
										
										
											2021-09-23 09:35:39 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Refreshes a Credential with fresh data from the database. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param credential | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | async function refreshCredential(credential: Credential): Promise<Credential> { | 
					
						
							|  |  |  |   const newCredential = await prisma.credential.findUnique({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       id: credential.id, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!newCredential) { | 
					
						
							|  |  |  |     return credential; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     return newCredential; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Refreshes the given set of credentials. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param credentials | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | async function refreshCredentials(credentials: Array<Credential>): Promise<Array<Credential>> { | 
					
						
							|  |  |  |   return await async.mapLimit(credentials, 5, refreshCredential); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  | function isAvailable(busyTimes: BufferedBusyTimes, time: string, length: number): boolean { | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   // Check for conflicts
 | 
					
						
							|  |  |  |   let t = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (Array.isArray(busyTimes) && busyTimes.length > 0) { | 
					
						
							|  |  |  |     busyTimes.forEach((busyTime) => { | 
					
						
							|  |  |  |       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, "[)")) { | 
					
						
							|  |  |  |         t = false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Check if slot end time is between start and end time
 | 
					
						
							|  |  |  |       if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) { | 
					
						
							|  |  |  |         t = false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Check if startTime is between slot
 | 
					
						
							|  |  |  |       if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) { | 
					
						
							|  |  |  |         t = false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return t; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isOutOfBounds( | 
					
						
							|  |  |  |   time: dayjs.ConfigType, | 
					
						
							| 
									
										
										
										
											2021-10-18 21:07:06 +00:00
										 |  |  |   { periodType, periodDays, periodCountCalendarDays, periodStartDate, periodEndDate, timeZone }: any // FIXME types
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | ): boolean { | 
					
						
							|  |  |  |   const date = dayjs(time); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   switch (periodType) { | 
					
						
							|  |  |  |     case "rolling": { | 
					
						
							|  |  |  |       const periodRollingEndDay = periodCountCalendarDays | 
					
						
							|  |  |  |         ? dayjs().tz(timeZone).add(periodDays, "days").endOf("day") | 
					
						
							| 
									
										
										
										
											2021-10-18 21:07:06 +00:00
										 |  |  |         : dayjs().tz(timeZone).addBusinessTime(periodDays, "days").endOf("day"); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       return date.endOf("day").isAfter(periodRollingEndDay); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case "range": { | 
					
						
							|  |  |  |       const periodRangeStartDay = dayjs(periodStartDate).tz(timeZone).endOf("day"); | 
					
						
							|  |  |  |       const periodRangeEndDay = dayjs(periodEndDate).tz(timeZone).endOf("day"); | 
					
						
							|  |  |  |       return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case "unlimited": | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  | const userSelect = Prisma.validator<Prisma.UserArgs>()({ | 
					
						
							|  |  |  |   select: { | 
					
						
							|  |  |  |     id: true, | 
					
						
							|  |  |  |     email: true, | 
					
						
							|  |  |  |     name: true, | 
					
						
							|  |  |  |     username: true, | 
					
						
							|  |  |  |     timeZone: true, | 
					
						
							|  |  |  |     credentials: true, | 
					
						
							|  |  |  |     bufferTime: true, | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |     destinationCalendar: true, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |     locale: true, | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |   }, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNames: string[]) => { | 
					
						
							|  |  |  |   const users = await prisma.user.findMany({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       username: { in: selectedUserNames }, | 
					
						
							| 
									
										
										
										
											2021-12-09 12:25:38 +00:00
										 |  |  |       eventTypes: { | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |         some: { | 
					
						
							| 
									
										
										
										
											2021-12-09 12:25:38 +00:00
										 |  |  |           id: eventTypeId, | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     select: { | 
					
						
							|  |  |  |       id: true, | 
					
						
							|  |  |  |       username: true, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       locale: true, | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const userNamesWithBookingCounts = await Promise.all( | 
					
						
							|  |  |  |     users.map(async (user) => ({ | 
					
						
							|  |  |  |       username: user.username, | 
					
						
							|  |  |  |       bookingCount: await prisma.booking.count({ | 
					
						
							|  |  |  |         where: { | 
					
						
							|  |  |  |           user: { | 
					
						
							|  |  |  |             id: user.id, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           startTime: { | 
					
						
							|  |  |  |             gt: new Date(), | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           eventTypeId, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }), | 
					
						
							|  |  |  |     })) | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return userNamesWithBookingCounts; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  | const getEventTypesFromDB = async (eventTypeId: number) => { | 
					
						
							|  |  |  |   return await prisma.eventType.findUnique({ | 
					
						
							| 
									
										
										
										
											2021-12-17 16:58:23 +00:00
										 |  |  |     rejectOnNotFound: true, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     where: { | 
					
						
							|  |  |  |       id: eventTypeId, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     select: { | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |       users: userSelect, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       team: { | 
					
						
							|  |  |  |         select: { | 
					
						
							|  |  |  |           id: true, | 
					
						
							|  |  |  |           name: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       title: true, | 
					
						
							|  |  |  |       length: true, | 
					
						
							|  |  |  |       eventName: true, | 
					
						
							|  |  |  |       schedulingType: true, | 
					
						
							| 
									
										
										
										
											2022-04-07 18:22:11 +00:00
										 |  |  |       description: true, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       periodType: true, | 
					
						
							|  |  |  |       periodStartDate: true, | 
					
						
							|  |  |  |       periodEndDate: true, | 
					
						
							|  |  |  |       periodDays: true, | 
					
						
							|  |  |  |       periodCountCalendarDays: true, | 
					
						
							|  |  |  |       requiresConfirmation: true, | 
					
						
							|  |  |  |       userId: true, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       price: true, | 
					
						
							|  |  |  |       currency: true, | 
					
						
							| 
									
										
										
										
											2022-02-01 21:48:40 +00:00
										 |  |  |       metadata: true, | 
					
						
							| 
									
										
										
										
											2022-01-21 21:35:31 +00:00
										 |  |  |       destinationCalendar: true, | 
					
						
							| 
									
										
										
										
											2022-03-28 18:07:13 +00:00
										 |  |  |       hideCalendarNotes: true, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type User = Prisma.UserGetPayload<typeof userSelect>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | 
					
						
							|  |  |  |   const reqBody = req.body as BookingCreateBody; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // handle dynamic user
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |   const dynamicUserList = Array.isArray(reqBody.user) | 
					
						
							|  |  |  |     ? getGroupName(req.body.user) | 
					
						
							|  |  |  |     : getUsernameList(reqBody.user as string); | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  |   const eventTypeSlug = reqBody.eventTypeSlug; | 
					
						
							|  |  |  |   const eventTypeId = reqBody.eventTypeId; | 
					
						
							|  |  |  |   const tAttendees = await getTranslation(reqBody.language ?? "en", "common"); | 
					
						
							|  |  |  |   const tGuests = await getTranslation("en", "common"); | 
					
						
							|  |  |  |   log.debug(`Booking eventType ${eventTypeId} started`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const isTimeInPast = (time: string): boolean => { | 
					
						
							|  |  |  |     return dayjs(time).isBefore(new Date(), "day"); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (isTimeInPast(reqBody.start)) { | 
					
						
							|  |  |  |     const error = { | 
					
						
							|  |  |  |       errorCode: "BookingDateInPast", | 
					
						
							|  |  |  |       message: "Attempting to create a meeting in the past.", | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     log.error(`Booking ${eventTypeId} failed`, error); | 
					
						
							|  |  |  |     return res.status(400).json(error); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  |   const eventType = !eventTypeId ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId); | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   if (!eventType) return res.status(404).json({ message: "eventType.notFound" }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  |   let users = !eventTypeId | 
					
						
							|  |  |  |     ? await prisma.user.findMany({ | 
					
						
							|  |  |  |         where: { | 
					
						
							|  |  |  |           username: { | 
					
						
							|  |  |  |             in: dynamicUserList, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         ...userSelect, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     : eventType.users; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   /* If this event was pre-relationship migration */ | 
					
						
							|  |  |  |   if (!users.length && eventType.userId) { | 
					
						
							| 
									
										
										
										
											2021-09-23 09:35:39 +00:00
										 |  |  |     const eventTypeUser = await prisma.user.findUnique({ | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       where: { | 
					
						
							|  |  |  |         id: eventType.userId, | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |       ...userSelect, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-09-23 09:35:39 +00:00
										 |  |  |     if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" }); | 
					
						
							|  |  |  |     users.push(eventTypeUser); | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |   const organizer = await prisma.user.findUnique({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       id: users[0].id, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     select: { | 
					
						
							|  |  |  |       locale: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const tOrganizer = await getTranslation(organizer?.locale ?? "en", "common"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |     const bookingCounts = await getUserNameWithBookingCounts( | 
					
						
							|  |  |  |       eventTypeId, | 
					
						
							|  |  |  |       ensureArray(reqBody.user) || users.map((user) => user.username) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  |     users = getLuckyUsers(users, bookingCounts); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |   const invitee = [ | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       email: reqBody.email, | 
					
						
							|  |  |  |       name: reqBody.name, | 
					
						
							|  |  |  |       timeZone: reqBody.timeZone, | 
					
						
							|  |  |  |       language: { translate: tAttendees, locale: reqBody.language ?? "en" }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   ]; | 
					
						
							| 
									
										
										
										
											2021-12-03 10:15:20 +00:00
										 |  |  |   const guests = (reqBody.guests || []).map((guest) => { | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     const g = { | 
					
						
							|  |  |  |       email: guest, | 
					
						
							|  |  |  |       name: "", | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       timeZone: reqBody.timeZone, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       language: { translate: tGuests, locale: "en" }, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     }; | 
					
						
							|  |  |  |     return g; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |   const teamMemberPromises = | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     eventType.schedulingType === SchedulingType.COLLECTIVE | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       ? users.slice(1).map(async function (user) { | 
					
						
							|  |  |  |           return { | 
					
						
							|  |  |  |             email: user.email || "", | 
					
						
							|  |  |  |             name: user.name || "", | 
					
						
							|  |  |  |             timeZone: user.timeZone, | 
					
						
							|  |  |  |             language: { | 
					
						
							|  |  |  |               translate: await getTranslation(user.locale ?? "en", "common"), | 
					
						
							|  |  |  |               locale: user.locale ?? "en", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       : []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |   const teamMembers = await Promise.all(teamMemberPromises); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   const attendeesList = [...invitee, ...guests, ...teamMembers]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 16:12:39 +00:00
										 |  |  |   const seed = `${users[0].username}:${dayjs(req.body.start).utc().format()}:${new Date().getTime()}`; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 11:04:12 +00:00
										 |  |  |   const eventNameObject = { | 
					
						
							|  |  |  |     attendeeName: reqBody.name || "Nameless", | 
					
						
							|  |  |  |     eventType: eventType.title, | 
					
						
							|  |  |  |     eventName: eventType.eventName, | 
					
						
							|  |  |  |     host: users[0].name || "Nameless", | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |     t: tOrganizer, | 
					
						
							| 
									
										
										
										
											2021-11-08 11:04:12 +00:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 18:22:11 +00:00
										 |  |  |   const additionalNotes = | 
					
						
							| 
									
										
										
										
											2021-12-07 21:05:29 +00:00
										 |  |  |     reqBody.notes + | 
					
						
							|  |  |  |     reqBody.customInputs.reduce( | 
					
						
							|  |  |  |       (str, input) => str + "<br /><br />" + input.label + ":<br />" + input.value, | 
					
						
							|  |  |  |       "" | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   const evt: CalendarEvent = { | 
					
						
							|  |  |  |     type: eventType.title, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |     title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
 | 
					
						
							| 
									
										
										
										
											2022-04-07 18:22:11 +00:00
										 |  |  |     description: eventType.description, | 
					
						
							|  |  |  |     additionalNotes, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     startTime: reqBody.start, | 
					
						
							|  |  |  |     endTime: reqBody.end, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     organizer: { | 
					
						
							| 
									
										
										
										
											2021-10-05 22:46:48 +00:00
										 |  |  |       name: users[0].name || "Nameless", | 
					
						
							|  |  |  |       email: users[0].email || "Email-less", | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       timeZone: users[0].timeZone, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       language: { translate: tOrganizer, locale: organizer?.locale ?? "en" }, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     }, | 
					
						
							|  |  |  |     attendees: attendeesList, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     location: reqBody.location, // Will be processed by the EventManager later.
 | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  |     /** For team events & dynamic collective events, we will need to handle each member destinationCalendar eventually */ | 
					
						
							| 
									
										
										
										
											2022-01-21 21:35:31 +00:00
										 |  |  |     destinationCalendar: eventType.destinationCalendar || users[0].destinationCalendar, | 
					
						
							| 
									
										
										
										
											2022-03-28 18:07:13 +00:00
										 |  |  |     hideCalendarNotes: eventType.hideCalendarNotes, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (eventType.schedulingType === SchedulingType.COLLECTIVE) { | 
					
						
							|  |  |  |     evt.team = { | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       members: users.map((user) => user.name || user.username || "Nameless"), | 
					
						
							|  |  |  |       name: eventType.team?.name || "Nameless", | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     }; // used for invitee emails
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 21:31:44 +00:00
										 |  |  |   // Initialize EventManager with credentials
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   const rescheduleUid = reqBody.rescheduleUid; | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |   async function getOriginalRescheduledBooking(uid: string) { | 
					
						
							|  |  |  |     return prisma.booking.findFirst({ | 
					
						
							|  |  |  |       where: { | 
					
						
							|  |  |  |         uid, | 
					
						
							|  |  |  |         status: { | 
					
						
							|  |  |  |           in: [BookingStatus.ACCEPTED, BookingStatus.CANCELLED], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       include: { | 
					
						
							|  |  |  |         attendees: { | 
					
						
							|  |  |  |           select: { | 
					
						
							|  |  |  |             name: true, | 
					
						
							|  |  |  |             email: true, | 
					
						
							|  |  |  |             locale: true, | 
					
						
							|  |  |  |             timeZone: true, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         user: { | 
					
						
							|  |  |  |           select: { | 
					
						
							|  |  |  |             id: true, | 
					
						
							|  |  |  |             name: true, | 
					
						
							|  |  |  |             email: true, | 
					
						
							|  |  |  |             locale: true, | 
					
						
							|  |  |  |             timeZone: true, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         payment: true, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   type BookingType = Prisma.PromiseReturnType<typeof getOriginalRescheduledBooking>; | 
					
						
							|  |  |  |   let originalRescheduledBooking: BookingType = null; | 
					
						
							|  |  |  |   if (rescheduleUid) { | 
					
						
							|  |  |  |     originalRescheduledBooking = await getOriginalRescheduledBooking(rescheduleUid); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-01 21:48:40 +00:00
										 |  |  |   async function createBooking() { | 
					
						
							|  |  |  |     // @TODO: check as metadata
 | 
					
						
							|  |  |  |     if (req.body.web3Details) { | 
					
						
							|  |  |  |       const { web3Details } = req.body; | 
					
						
							|  |  |  |       await verifyAccount(web3Details.userSignature, web3Details.userWallet); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |     if (originalRescheduledBooking) { | 
					
						
							|  |  |  |       evt.title = originalRescheduledBooking?.title || evt.title; | 
					
						
							|  |  |  |       evt.description = originalRescheduledBooking?.description || evt.additionalNotes; | 
					
						
							|  |  |  |       evt.location = originalRescheduledBooking?.location; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-06 17:20:30 +00:00
										 |  |  |     const eventTypeRel = !eventTypeId | 
					
						
							|  |  |  |       ? {} | 
					
						
							|  |  |  |       : { | 
					
						
							|  |  |  |           connect: { | 
					
						
							|  |  |  |             id: eventTypeId, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const dynamicEventSlugRef = !eventTypeId ? eventTypeSlug : null; | 
					
						
							|  |  |  |     const dynamicGroupSlugRef = !eventTypeId ? (reqBody.user as string).toLowerCase() : null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |     const newBookingData: Prisma.BookingCreateInput = { | 
					
						
							|  |  |  |       uid, | 
					
						
							|  |  |  |       title: evt.title, | 
					
						
							|  |  |  |       startTime: dayjs(evt.startTime).toDate(), | 
					
						
							|  |  |  |       endTime: dayjs(evt.endTime).toDate(), | 
					
						
							|  |  |  |       description: evt.additionalNotes, | 
					
						
							|  |  |  |       confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid, | 
					
						
							|  |  |  |       location: evt.location, | 
					
						
							|  |  |  |       eventType: eventTypeRel, | 
					
						
							|  |  |  |       attendees: { | 
					
						
							|  |  |  |         createMany: { | 
					
						
							|  |  |  |           data: evt.attendees.map((attendee) => { | 
					
						
							|  |  |  |             //if attendee is team member, it should fetch their locale not booker's locale
 | 
					
						
							|  |  |  |             //perhaps make email fetch request to see if his locale is stored, else
 | 
					
						
							|  |  |  |             const retObj = { | 
					
						
							|  |  |  |               name: attendee.name, | 
					
						
							|  |  |  |               email: attendee.email, | 
					
						
							|  |  |  |               timeZone: attendee.timeZone, | 
					
						
							|  |  |  |               locale: attendee.language.locale, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |             return retObj; | 
					
						
							|  |  |  |           }), | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |       dynamicEventSlugRef, | 
					
						
							|  |  |  |       dynamicGroupSlugRef, | 
					
						
							|  |  |  |       user: { | 
					
						
							|  |  |  |         connect: { | 
					
						
							|  |  |  |           id: users[0].id, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |       }, | 
					
						
							|  |  |  |       destinationCalendar: evt.destinationCalendar | 
					
						
							|  |  |  |         ? { | 
					
						
							|  |  |  |             connect: { id: evt.destinationCalendar.id }, | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         : undefined, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     if (originalRescheduledBooking) { | 
					
						
							|  |  |  |       newBookingData["paid"] = originalRescheduledBooking.paid; | 
					
						
							|  |  |  |       newBookingData["fromReschedule"] = originalRescheduledBooking.uid; | 
					
						
							|  |  |  |       if (newBookingData.attendees?.createMany?.data) { | 
					
						
							|  |  |  |         newBookingData.attendees.createMany.data = originalRescheduledBooking.attendees; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const createBookingObj = { | 
					
						
							|  |  |  |       include: { | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |         user: { | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |           select: { email: true, name: true, timeZone: true }, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |         attendees: true, | 
					
						
							|  |  |  |         payment: true, | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |       data: newBookingData, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (originalRescheduledBooking?.paid && originalRescheduledBooking?.payment) { | 
					
						
							|  |  |  |       const bookingPayment = originalRescheduledBooking?.payment?.find((payment) => payment.success); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (bookingPayment) { | 
					
						
							|  |  |  |         createBookingObj.data.payment = { | 
					
						
							|  |  |  |           connect: { id: bookingPayment.id }, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return prisma.booking.create(createBookingObj); | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   let results: EventResult[] = []; | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   let referencesToCreate: PartialReference[] = []; | 
					
						
							|  |  |  |   let user: User | null = null; | 
					
						
							| 
									
										
										
										
											2021-10-05 22:46:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |   /** Let's start checking for availability */ | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   for (const currentUser of users) { | 
					
						
							|  |  |  |     if (!currentUser) { | 
					
						
							|  |  |  |       console.error(`currentUser not found`); | 
					
						
							|  |  |  |       return; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     if (!user) user = currentUser; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     const selectedCalendars = await prisma.selectedCalendar.findMany({ | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       where: { | 
					
						
							|  |  |  |         userId: currentUser.id, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     const credentials = currentUser.credentials; | 
					
						
							| 
									
										
										
										
											2022-04-13 17:22:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     const calendarBusyTimes: EventBusyDate[] = await prisma.booking | 
					
						
							|  |  |  |       .findMany({ | 
					
						
							|  |  |  |         where: { | 
					
						
							| 
									
										
										
										
											2022-04-13 17:22:27 +00:00
										 |  |  |           AND: [ | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               userId: currentUser.id, | 
					
						
							|  |  |  |               eventTypeId: eventTypeId, | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               OR: [ | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   status: "ACCEPTED", | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   status: "PENDING", | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           ], | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |         }, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .then((bookings) => bookings.map((booking) => ({ end: booking.endTime, start: booking.startTime }))); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     if (credentials) { | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |       await getBusyCalendarTimes(credentials, reqBody.start, reqBody.end, selectedCalendars).then( | 
					
						
							|  |  |  |         (busyTimes) => calendarBusyTimes.push(...busyTimes) | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |       const videoBusyTimes = (await getBusyVideoTimes(credentials)).filter(notEmpty); | 
					
						
							|  |  |  |       calendarBusyTimes.push(...videoBusyTimes); | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     console.log("calendarBusyTimes==>>>", calendarBusyTimes); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     const bufferedBusyTimes: BufferedBusyTimes = calendarBusyTimes.map((a) => ({ | 
					
						
							|  |  |  |       start: dayjs(a.start).subtract(currentUser.bufferTime, "minute").toString(), | 
					
						
							|  |  |  |       end: dayjs(a.end).add(currentUser.bufferTime, "minute").toString(), | 
					
						
							|  |  |  |     })); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     let isAvailableToBeBooked = true; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       isAvailableToBeBooked = isAvailable(bufferedBusyTimes, reqBody.start, eventType.length); | 
					
						
							|  |  |  |     } catch { | 
					
						
							|  |  |  |       log.debug({ | 
					
						
							|  |  |  |         message: "Unable set isAvailableToBeBooked. Using true. ", | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!isAvailableToBeBooked) { | 
					
						
							|  |  |  |       const error = { | 
					
						
							|  |  |  |         errorCode: "BookingUserUnAvailable", | 
					
						
							|  |  |  |         message: `${currentUser.name} is unavailable at this time.`, | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |       log.debug(`Booking ${currentUser.name} failed`, error); | 
					
						
							|  |  |  |       res.status(409).json(error); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  |     let timeOutOfBounds = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       timeOutOfBounds = isOutOfBounds(reqBody.start, { | 
					
						
							|  |  |  |         periodType: eventType.periodType, | 
					
						
							|  |  |  |         periodDays: eventType.periodDays, | 
					
						
							|  |  |  |         periodEndDate: eventType.periodEndDate, | 
					
						
							|  |  |  |         periodStartDate: eventType.periodStartDate, | 
					
						
							|  |  |  |         periodCountCalendarDays: eventType.periodCountCalendarDays, | 
					
						
							|  |  |  |         timeZone: currentUser.timeZone, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch { | 
					
						
							|  |  |  |       log.debug({ | 
					
						
							|  |  |  |         message: "Unable set timeOutOfBounds. Using false. ", | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-03-07 17:55:24 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (timeOutOfBounds) { | 
					
						
							|  |  |  |       const error = { | 
					
						
							|  |  |  |         errorCode: "BookingUserUnAvailable", | 
					
						
							|  |  |  |         message: `${currentUser.name} is unavailable at this time.`, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       log.debug(`Booking ${currentUser.name} failed`, error); | 
					
						
							|  |  |  |       res.status(400).json(error); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   type Booking = Prisma.PromiseReturnType<typeof createBooking>; | 
					
						
							|  |  |  |   let booking: Booking | null = null; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     booking = await createBooking(); | 
					
						
							|  |  |  |     evt.uid = booking.uid; | 
					
						
							|  |  |  |   } catch (_err) { | 
					
						
							|  |  |  |     const err = getErrorFromUnknown(_err); | 
					
						
							|  |  |  |     log.error(`Booking ${eventTypeId} failed`, "Error when saving booking to db", err.message); | 
					
						
							|  |  |  |     if (err.code === "P2002") { | 
					
						
							|  |  |  |       res.status(409).json({ message: "booking.conflict" }); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     res.status(500).end(); | 
					
						
							|  |  |  |     return; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-10-05 22:46:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (!user) throw Error("Can't continue, user not found."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-23 09:35:39 +00:00
										 |  |  |   // After polling videoBusyTimes, credentials might have been changed due to refreshment, so query them again.
 | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |   const credentials = await refreshCredentials(user.credentials); | 
					
						
							|  |  |  |   const eventManager = new EventManager({ ...user, credentials }); | 
					
						
							| 
									
										
										
										
											2021-09-17 21:31:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |   if (originalRescheduledBooking?.uid) { | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     // Use EventManager to conditionally use all needed integrations.
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |     const updateManager = await eventManager.update(evt, originalRescheduledBooking.uid, booking.id); | 
					
						
							| 
									
										
										
										
											2022-04-03 17:59:40 +00:00
										 |  |  |     // This gets overridden when updating the event - to check if notes have been hidden or not. We just reset this back
 | 
					
						
							|  |  |  |     // to the default description when we are sending the emails.
 | 
					
						
							| 
									
										
										
										
											2022-04-07 18:22:11 +00:00
										 |  |  |     evt.description = eventType.description; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     results = updateManager.results; | 
					
						
							|  |  |  |     referencesToCreate = updateManager.referencesToCreate; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (results.length > 0 && results.every((res) => !res.success)) { | 
					
						
							|  |  |  |       const error = { | 
					
						
							|  |  |  |         errorCode: "BookingReschedulingMeetingFailed", | 
					
						
							|  |  |  |         message: "Booking Rescheduling failed", | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       log.error(`Booking ${user.name} failed`, error, results); | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       const metadata: AdditionInformation = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (results.length) { | 
					
						
							|  |  |  |         // TODO: Handle created event metadata more elegantly
 | 
					
						
							| 
									
										
										
										
											2022-02-10 10:44:46 +00:00
										 |  |  |         const [updatedEvent] = Array.isArray(results[0].updatedEvent) | 
					
						
							|  |  |  |           ? results[0].updatedEvent | 
					
						
							|  |  |  |           : [results[0].updatedEvent]; | 
					
						
							|  |  |  |         if (updatedEvent) { | 
					
						
							|  |  |  |           metadata.hangoutLink = updatedEvent.hangoutLink; | 
					
						
							|  |  |  |           metadata.conferenceData = updatedEvent.conferenceData; | 
					
						
							|  |  |  |           metadata.entryPoints = updatedEvent.entryPoints; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await sendRescheduledEmails({ ...evt, additionInformation: metadata }); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-26 16:17:24 +00:00
										 |  |  |     // If it's not a reschedule, doesn't require confirmation and there's no price,
 | 
					
						
							|  |  |  |     // Create a booking
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |   } else if (!eventType.requiresConfirmation && !eventType.price) { | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     // Use EventManager to conditionally use all needed integrations.
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     const createManager = await eventManager.create(evt); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 17:59:40 +00:00
										 |  |  |     // This gets overridden when creating the event - to check if notes have been hidden or not. We just reset this back
 | 
					
						
							|  |  |  |     // to the default description when we are sending the emails.
 | 
					
						
							| 
									
										
										
										
											2022-04-07 18:22:11 +00:00
										 |  |  |     evt.description = eventType.description; | 
					
						
							| 
									
										
										
										
											2022-04-03 17:59:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     results = createManager.results; | 
					
						
							|  |  |  |     referencesToCreate = createManager.referencesToCreate; | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     if (results.length > 0 && results.every((res) => !res.success)) { | 
					
						
							|  |  |  |       const error = { | 
					
						
							|  |  |  |         errorCode: "BookingCreatingMeetingFailed", | 
					
						
							|  |  |  |         message: "Booking failed", | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       log.error(`Booking ${user.username} failed`, error, results); | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       const metadata: AdditionInformation = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (results.length) { | 
					
						
							|  |  |  |         // TODO: Handle created event metadata more elegantly
 | 
					
						
							|  |  |  |         metadata.hangoutLink = results[0].createdEvent?.hangoutLink; | 
					
						
							|  |  |  |         metadata.conferenceData = results[0].createdEvent?.conferenceData; | 
					
						
							|  |  |  |         metadata.entryPoints = results[0].createdEvent?.entryPoints; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       await sendScheduledEmails({ ...evt, additionInformation: metadata }); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 21:31:44 +00:00
										 |  |  |   if (eventType.requiresConfirmation && !rescheduleUid) { | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |     await sendOrganizerRequestEmail(evt); | 
					
						
							| 
									
										
										
										
											2022-03-07 18:18:23 +00:00
										 |  |  |     await sendAttendeeRequestEmail(evt, attendeesList[0]); | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |   if (typeof eventType.price === "number" && eventType.price > 0 && !originalRescheduledBooking?.paid) { | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |     try { | 
					
						
							|  |  |  |       const [firstStripeCredential] = user.credentials.filter((cred) => cred.type == "stripe_payment"); | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       if (!booking.user) booking.user = user; | 
					
						
							|  |  |  |       const payment = await handlePayment(evt, eventType, firstStripeCredential, booking); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       res.status(201).json({ ...booking, message: "Payment required", paymentUid: payment.uid }); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       log.error(`Creating payment failed`, e); | 
					
						
							|  |  |  |       res.status(500).json({ message: "Payment Failed" }); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   log.debug(`Booking ${user.username} completed`); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-02 16:24:57 +00:00
										 |  |  |   const eventTrigger: WebhookTriggerEvents = rescheduleUid ? "BOOKING_RESCHEDULED" : "BOOKING_CREATED"; | 
					
						
							|  |  |  |   const subscriberOptions = { | 
					
						
							|  |  |  |     userId: user.id, | 
					
						
							|  |  |  |     eventTypeId, | 
					
						
							|  |  |  |     triggerEvent: eventTrigger, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 15:14:47 +00:00
										 |  |  |   // Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED
 | 
					
						
							| 
									
										
										
										
											2022-03-02 16:24:57 +00:00
										 |  |  |   const subscribers = await getSubscribers(subscriberOptions); | 
					
						
							| 
									
										
										
										
											2021-12-03 10:15:20 +00:00
										 |  |  |   console.log("evt:", { | 
					
						
							|  |  |  |     ...evt, | 
					
						
							|  |  |  |     metadata: reqBody.metadata, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-11-22 11:37:07 +00:00
										 |  |  |   const promises = subscribers.map((sub) => | 
					
						
							| 
									
										
										
										
											2021-12-03 10:15:20 +00:00
										 |  |  |     sendPayload( | 
					
						
							|  |  |  |       eventTrigger, | 
					
						
							|  |  |  |       new Date().toISOString(), | 
					
						
							|  |  |  |       sub.subscriberUrl, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         ...evt, | 
					
						
							| 
									
										
										
										
											2022-02-19 00:09:07 +00:00
										 |  |  |         rescheduleUid, | 
					
						
							| 
									
										
										
										
											2021-12-03 10:15:20 +00:00
										 |  |  |         metadata: reqBody.metadata, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       sub.payloadTemplate | 
					
						
							|  |  |  |     ).catch((e) => { | 
					
						
							|  |  |  |       console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e); | 
					
						
							|  |  |  |     }) | 
					
						
							| 
									
										
										
										
											2021-10-07 15:14:47 +00:00
										 |  |  |   ); | 
					
						
							|  |  |  |   await Promise.all(promises); | 
					
						
							| 
									
										
										
										
											2022-03-24 23:29:32 +00:00
										 |  |  |   // Avoid passing referencesToCreate with id unique constrain values
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   await prisma.booking.update({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       uid: booking.uid, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     data: { | 
					
						
							|  |  |  |       references: { | 
					
						
							|  |  |  |         createMany: { | 
					
						
							|  |  |  |           data: referencesToCreate, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 21:31:44 +00:00
										 |  |  |   // booking successful
 | 
					
						
							| 
									
										
										
										
											2021-09-14 08:45:28 +00:00
										 |  |  |   return res.status(201).json(booking); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-03 16:18:31 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function getLuckyUsers( | 
					
						
							|  |  |  |   users: User[], | 
					
						
							|  |  |  |   bookingCounts: Prisma.PromiseReturnType<typeof getUserNameWithBookingCounts> | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  |   if (!bookingCounts.length) users.slice(0, 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1)); | 
					
						
							|  |  |  |   const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username); | 
					
						
							|  |  |  |   return luckyUser ? [luckyUser] : users; | 
					
						
							|  |  |  | } |