diff --git a/apps/web/pages/api/book/request-reschedule.ts b/apps/web/pages/api/book/request-reschedule.ts index a4c96646..ba57e49f 100644 --- a/apps/web/pages/api/book/request-reschedule.ts +++ b/apps/web/pages/api/book/request-reschedule.ts @@ -1,4 +1,4 @@ -import { BookingStatus, User, Booking, Attendee, BookingReference } from "@prisma/client"; +import { BookingStatus, User, Booking, Attendee, BookingReference, EventType } from "@prisma/client"; import dayjs from "dayjs"; import type { NextApiRequest, NextApiResponse } from "next"; import { getSession } from "next-auth/react"; @@ -29,7 +29,7 @@ const rescheduleSchema = z.object({ rescheduleReason: z.string().optional(), }); -const findUserOwnerByUserId = async (userId: number) => { +const findUserDataByUserId = async (userId: number) => { return await prisma.user.findUnique({ rejectOnNotFound: true, where: { @@ -57,10 +57,10 @@ const handler = async ( bookingId, rescheduleReason: cancellationReason, }: { bookingId: string; rescheduleReason: string; cancellationReason: string } = req.body; - let userOwner: Awaited>; + let userOwner: Awaited>; try { if (session?.user?.id) { - userOwner = await findUserOwnerByUserId(session?.user.id); + userOwner = await findUserDataByUserId(session?.user.id); } else { return res.status(501); } @@ -76,6 +76,10 @@ const handler = async ( location: true, attendees: true, references: true, + userId: true, + dynamicEventSlugRef: true, + dynamicGroupSlugRef: true, + destinationCalendar: true, }, rejectOnNotFound: true, where: { @@ -88,18 +92,21 @@ const handler = async ( }, }); - if (bookingToReschedule && bookingToReschedule.eventTypeId && userOwner) { - const event = await prisma.eventType.findFirst({ - select: { - title: true, - users: true, - schedulingType: true, - }, - rejectOnNotFound: true, - where: { - id: bookingToReschedule.eventTypeId, - }, - }); + if (bookingToReschedule && userOwner) { + let event: Partial = {}; + if (bookingToReschedule.eventTypeId) { + event = await prisma.eventType.findFirst({ + select: { + title: true, + users: true, + schedulingType: true, + }, + rejectOnNotFound: true, + where: { + id: bookingToReschedule.eventTypeId, + }, + }); + } await prisma.booking.update({ where: { id: bookingToReschedule.id, @@ -136,7 +143,7 @@ const handler = async ( const builder = new CalendarEventBuilder(); builder.init({ title: bookingToReschedule.title, - type: event.title, + type: event && event.title ? event.title : bookingToReschedule.title, startTime: bookingToReschedule.startTime.toISOString(), endTime: bookingToReschedule.endTime.toISOString(), attendees: usersToPeopleType( @@ -149,9 +156,13 @@ const handler = async ( const director = new CalendarEventDirector(); director.setBuilder(builder); - director.setExistingBooking(bookingToReschedule as unknown as Booking); + director.setExistingBooking(bookingToReschedule); director.setCancellationReason(cancellationReason); - await director.buildForRescheduleEmail(); + if (!!event) { + await director.buildWithoutEventTypeForRescheduleEmail(); + } else { + await director.buildForRescheduleEmail(); + } // Handling calendar and videos cancellation // This can set previous time as available, until virtual calendar is done @@ -174,6 +185,31 @@ const handler = async ( } }); + // Updating attendee destinationCalendar if required + if ( + bookingToReschedule.destinationCalendar && + bookingToReschedule.destinationCalendar.userId && + bookingToReschedule.destinationCalendar.integration.endsWith("_calendar") + ) { + const { destinationCalendar } = bookingToReschedule; + if (destinationCalendar.userId) { + const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter( + (ref) => !!credentialsMap.get(ref.type) + ); + const attendeeData = await findUserDataByUserId(destinationCalendar.userId); + const attendeeCredentialsMap = new Map(); + attendeeData.credentials.forEach((credential) => { + attendeeCredentialsMap.set(credential.type, credential); + }); + bookingRefsFiltered.forEach((bookingRef) => { + if (bookingRef.uid) { + const calendar = getCalendar(attendeeCredentialsMap.get(destinationCalendar.integration)); + calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent); + } + }); + } + } + // Send emails await sendRequestRescheduleEmail(builder.calendarEvent, { rescheduleLink: builder.rescheduleLink, diff --git a/packages/core/builders/CalendarEvent/builder.ts b/packages/core/builders/CalendarEvent/builder.ts index 11c55cdc..44697080 100644 --- a/packages/core/builders/CalendarEvent/builder.ts +++ b/packages/core/builders/CalendarEvent/builder.ts @@ -1,4 +1,4 @@ -import { Prisma } from "@prisma/client"; +import { Prisma, Booking } from "@prisma/client"; import dayjs from "dayjs"; import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; @@ -80,7 +80,7 @@ export class CalendarEventBuilder implements ICalendarEventBuilder { } users.push(eventTypeUser); } - this.users = users; + this.setUsers(users); } public buildAttendeesList() { @@ -273,17 +273,54 @@ export class CalendarEventBuilder implements ICalendarEventBuilder { this.calendarEvent.cancellationReason = cancellationReason; } - public buildRescheduleLink(originalBookingUId: string) { - if (!this.eventType) { - throw new Error("Run buildEventObjectFromInnerClass before this function"); - } - const isTeam = !!this.eventType.teamId; + public setUsers(users: User[]) { + this.users = users; + } - const queryParams = new URLSearchParams(); - queryParams.set("rescheduleUid", `${originalBookingUId}`); - const rescheduleLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/${ - isTeam ? `/team/${this.eventType.team?.slug}` : this.users[0].username - }/${this.eventType.slug}?${queryParams.toString()}`; - this.rescheduleLink = rescheduleLink; + public async setUsersFromId(userId: User["id"]) { + let resultUser: User | null; + try { + resultUser = await prisma.user.findUnique({ + rejectOnNotFound: true, + where: { + id: userId, + }, + ...userSelect, + }); + this.setUsers([resultUser]); + } catch (error) { + throw new Error("getUsersById.users.notFound"); + } + } + + public buildRescheduleLink(booking: Partial, eventType?: CalendarEventBuilder["eventType"]) { + try { + if (!booking) { + throw new Error("Parameter booking is required to build reschedule link"); + } + const isTeam = !!eventType && !!eventType.teamId; + const isDynamic = booking?.dynamicEventSlugRef && booking?.dynamicGroupSlugRef; + + let slug = ""; + if (isTeam && eventType?.team?.slug) { + slug = `/team/${eventType.team?.slug}`; + } else if (isDynamic) { + const dynamicSlug = isDynamic ? `${booking.dynamicGroupSlugRef}/${booking.dynamicEventSlugRef}` : ""; + slug = dynamicSlug; + } else if (eventType?.slug) { + slug = `${this.users[0].username}/${eventType.slug}`; + } + + const queryParams = new URLSearchParams(); + queryParams.set("rescheduleUid", `${booking.uid}`); + slug = `${slug}?${queryParams.toString()}`; + + const rescheduleLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/${slug}`; + this.rescheduleLink = rescheduleLink; + } catch (error) { + if (error instanceof Error) { + throw new Error(`buildRescheduleLink.error: ${error.message}`); + } + } } } diff --git a/packages/core/builders/CalendarEvent/director.ts b/packages/core/builders/CalendarEvent/director.ts index b33311b8..3b9e7a3c 100644 --- a/packages/core/builders/CalendarEvent/director.ts +++ b/packages/core/builders/CalendarEvent/director.ts @@ -11,7 +11,21 @@ export class CalendarEventDirector { this.builder = builder; } - public setExistingBooking(booking: Booking) { + public setExistingBooking( + booking: Pick< + Booking, + | "id" + | "uid" + | "title" + | "startTime" + | "endTime" + | "eventTypeId" + | "userId" + | "dynamicEventSlugRef" + | "dynamicGroupSlugRef" + | "location" + > + ) { this.existingBooking = booking; } @@ -29,9 +43,23 @@ export class CalendarEventDirector { this.builder.setCancellationReason(this.cancellationReason); this.builder.setDescription(this.builder.eventType.description); this.builder.setNotes(this.existingBooking.description); - this.builder.buildRescheduleLink(this.existingBooking.uid); + this.builder.buildRescheduleLink(this.existingBooking, this.builder.eventType); } else { throw new Error("buildForRescheduleEmail.missing.params.required"); } } + + public async buildWithoutEventTypeForRescheduleEmail() { + if (this.existingBooking && this.existingBooking.userId && this.existingBooking.uid) { + await this.builder.setUsersFromId(this.existingBooking.userId); + this.builder.buildAttendeesList(); + this.builder.setLocation(this.existingBooking.location); + this.builder.setUId(this.existingBooking.uid); + this.builder.setCancellationReason(this.cancellationReason); + this.builder.setDescription(this.existingBooking.description); + await this.builder.buildRescheduleLink(this.existingBooking); + } else { + throw new Error("buildWithoutEventTypeForRescheduleEmail.missing.params.required"); + } + } }