| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  | import { Prisma, User, Booking, SchedulingType, BookingStatus } from "@prisma/client"; | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  | import type { NextApiRequest, NextApiResponse } from "next"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 22:00:30 +00:00
										 |  |  | import EventManager from "@calcom/core/EventManager"; | 
					
						
							|  |  |  | import logger from "@calcom/lib/logger"; | 
					
						
							|  |  |  | import type { AdditionInformation } from "@calcom/types/Calendar"; | 
					
						
							|  |  |  | import type { CalendarEvent } from "@calcom/types/Calendar"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | import { refund } from "@ee/lib/stripe/server"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 18:25:58 +00:00
										 |  |  | import { asStringOrNull } from "@lib/asStringOrNull"; | 
					
						
							| 
									
										
										
										
											2021-09-03 20:51:21 +00:00
										 |  |  | import { getSession } from "@lib/auth"; | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | import { sendDeclinedEmails } from "@lib/emails/email-manager"; | 
					
						
							|  |  |  | import { sendScheduledEmails } from "@lib/emails/email-manager"; | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  | import prisma from "@lib/prisma"; | 
					
						
							| 
									
										
										
										
											2021-10-26 16:17:24 +00:00
										 |  |  | import { BookingConfirmBody } from "@lib/types/booking"; | 
					
						
							| 
									
										
										
										
											2021-09-22 19:52:38 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  | import { getTranslation } from "@server/lib/i18n"; | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  | const authorized = async ( | 
					
						
							|  |  |  |   currentUser: Pick<User, "id">, | 
					
						
							|  |  |  |   booking: Pick<Booking, "eventTypeId" | "userId"> | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   // if the organizer
 | 
					
						
							|  |  |  |   if (booking.userId === currentUser.id) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const eventType = await prisma.eventType.findUnique({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       id: booking.eventTypeId || undefined, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     select: { | 
					
						
							|  |  |  |       schedulingType: true, | 
					
						
							|  |  |  |       users: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   if ( | 
					
						
							|  |  |  |     eventType?.schedulingType === SchedulingType.COLLECTIVE && | 
					
						
							|  |  |  |     eventType.users.find((user) => user.id === currentUser.id) | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | const log = logger.getChildLogger({ prefix: ["[api] book:user"] }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-10 17:42:06 +00:00
										 |  |  | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |   const session = await getSession({ req: req }); | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  |   if (!session?.user?.id) { | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     return res.status(401).json({ message: "Not authenticated" }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 16:17:24 +00:00
										 |  |  |   const reqBody = req.body as BookingConfirmBody; | 
					
						
							|  |  |  |   const bookingId = reqBody.id; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |   if (!bookingId) { | 
					
						
							|  |  |  |     return res.status(400).json({ message: "bookingId missing" }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const currentUser = await prisma.user.findFirst({ | 
					
						
							|  |  |  |     where: { | 
					
						
							|  |  |  |       id: session.user.id, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     select: { | 
					
						
							|  |  |  |       id: true, | 
					
						
							| 
									
										
										
										
											2021-12-06 13:25:22 +00:00
										 |  |  |       credentials: { | 
					
						
							|  |  |  |         orderBy: { id: "desc" as Prisma.SortOrder }, | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |       timeZone: true, | 
					
						
							|  |  |  |       email: true, | 
					
						
							|  |  |  |       name: true, | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |       username: true, | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |       destinationCalendar: true, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       locale: true, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  |   if (!currentUser) { | 
					
						
							|  |  |  |     return res.status(404).json({ message: "User not found" }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |   const tOrganizer = await getTranslation(currentUser.locale ?? "en", "common"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |   if (req.method === "PATCH") { | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     const booking = await prisma.booking.findFirst({ | 
					
						
							|  |  |  |       where: { | 
					
						
							|  |  |  |         id: bookingId, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       select: { | 
					
						
							|  |  |  |         title: true, | 
					
						
							|  |  |  |         description: true, | 
					
						
							|  |  |  |         startTime: true, | 
					
						
							|  |  |  |         endTime: true, | 
					
						
							|  |  |  |         confirmed: true, | 
					
						
							|  |  |  |         attendees: true, | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |         eventTypeId: true, | 
					
						
							| 
									
										
										
										
											2021-07-25 12:37:22 +00:00
										 |  |  |         location: true, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |         userId: true, | 
					
						
							|  |  |  |         id: true, | 
					
						
							|  |  |  |         uid: true, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |         payment: true, | 
					
						
							| 
									
										
										
										
											2022-01-21 21:35:31 +00:00
										 |  |  |         destinationCalendar: true, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |     if (!booking) { | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |       return res.status(404).json({ message: "booking not found" }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!(await authorized(currentUser, booking))) { | 
					
						
							|  |  |  |       return res.status(401).end(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     if (booking.confirmed) { | 
					
						
							|  |  |  |       return res.status(400).json({ message: "booking already confirmed" }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |     const attendeesListPromises = booking.attendees.map(async (attendee) => { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         name: attendee.name, | 
					
						
							|  |  |  |         email: attendee.email, | 
					
						
							|  |  |  |         timeZone: attendee.timeZone, | 
					
						
							|  |  |  |         language: { | 
					
						
							|  |  |  |           translate: await getTranslation(attendee.locale ?? "en", "common"), | 
					
						
							|  |  |  |           locale: attendee.locale ?? "en", | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const attendeesList = await Promise.all(attendeesListPromises); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     const evt: CalendarEvent = { | 
					
						
							|  |  |  |       type: booking.title, | 
					
						
							|  |  |  |       title: booking.title, | 
					
						
							|  |  |  |       description: booking.description, | 
					
						
							|  |  |  |       startTime: booking.startTime.toISOString(), | 
					
						
							|  |  |  |       endTime: booking.endTime.toISOString(), | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |       organizer: { | 
					
						
							|  |  |  |         email: currentUser.email, | 
					
						
							|  |  |  |         name: currentUser.name || "Unnamed", | 
					
						
							|  |  |  |         timeZone: currentUser.timeZone, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |         language: { translate: tOrganizer, locale: currentUser.locale ?? "en" }, | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-01-27 20:32:53 +00:00
										 |  |  |       attendees: attendeesList, | 
					
						
							| 
									
										
										
										
											2021-10-26 16:17:24 +00:00
										 |  |  |       location: booking.location ?? "", | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  |       uid: booking.uid, | 
					
						
							| 
									
										
										
										
											2022-01-21 21:35:31 +00:00
										 |  |  |       destinationCalendar: booking?.destinationCalendar || currentUser.destinationCalendar, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 16:17:24 +00:00
										 |  |  |     if (reqBody.confirmed) { | 
					
						
							| 
									
										
										
										
											2021-12-09 15:51:37 +00:00
										 |  |  |       const eventManager = new EventManager(currentUser); | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  |       const scheduleResult = await eventManager.create(evt); | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |       const results = scheduleResult.results; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (results.length > 0 && results.every((res) => !res.success)) { | 
					
						
							|  |  |  |         const error = { | 
					
						
							|  |  |  |           errorCode: "BookingCreatingMeetingFailed", | 
					
						
							|  |  |  |           message: "Booking failed", | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         log.error(`Booking ${currentUser.username} failed`, error, results); | 
					
						
							|  |  |  |       } 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; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |         try { | 
					
						
							|  |  |  |           await sendScheduledEmails({ ...evt, additionInformation: metadata }); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           log.error(error); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 21:25:24 +00:00
										 |  |  |       // @NOTE: be careful with this as if any error occurs before this booking doesn't get confirmed
 | 
					
						
							|  |  |  |       // Should perform update on booking (confirm) -> then trigger the rest handlers
 | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |       await prisma.booking.update({ | 
					
						
							|  |  |  |         where: { | 
					
						
							|  |  |  |           id: bookingId, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         data: { | 
					
						
							|  |  |  |           confirmed: true, | 
					
						
							|  |  |  |           references: { | 
					
						
							|  |  |  |             create: scheduleResult.referencesToCreate, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |       res.status(204).end(); | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2021-09-22 18:36:13 +00:00
										 |  |  |       await refund(booking, evt); | 
					
						
							| 
									
										
										
										
											2022-02-09 18:25:58 +00:00
										 |  |  |       const rejectionReason = asStringOrNull(req.body.reason) || ""; | 
					
						
							| 
									
										
										
										
											2022-02-10 09:45:27 +00:00
										 |  |  |       evt.rejectionReason = rejectionReason; | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |       await prisma.booking.update({ | 
					
						
							|  |  |  |         where: { | 
					
						
							|  |  |  |           id: bookingId, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         data: { | 
					
						
							|  |  |  |           rejected: true, | 
					
						
							| 
									
										
										
										
											2021-11-04 22:24:15 +00:00
										 |  |  |           status: BookingStatus.REJECTED, | 
					
						
							| 
									
										
										
										
											2022-02-09 18:25:58 +00:00
										 |  |  |           rejectionReason: rejectionReason, | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-11-26 11:03:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       await sendDeclinedEmails(evt); | 
					
						
							| 
									
										
										
										
											2021-10-25 13:05:21 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 00:50:52 +00:00
										 |  |  |       res.status(204).end(); | 
					
						
							| 
									
										
										
										
											2021-07-17 12:30:29 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |