Fix/reschedule on dynamic events (#2657)

* Reschedule for dynamic events

* Fix lint

* Handling attendee calendar event cancellation

Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
alannnc 2022-05-04 20:03:36 -05:00 committed by GitHub
parent 6ec2b20a23
commit df7abdfc06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 34 deletions

View file

@ -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 dayjs from "dayjs";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react"; import { getSession } from "next-auth/react";
@ -29,7 +29,7 @@ const rescheduleSchema = z.object({
rescheduleReason: z.string().optional(), rescheduleReason: z.string().optional(),
}); });
const findUserOwnerByUserId = async (userId: number) => { const findUserDataByUserId = async (userId: number) => {
return await prisma.user.findUnique({ return await prisma.user.findUnique({
rejectOnNotFound: true, rejectOnNotFound: true,
where: { where: {
@ -57,10 +57,10 @@ const handler = async (
bookingId, bookingId,
rescheduleReason: cancellationReason, rescheduleReason: cancellationReason,
}: { bookingId: string; rescheduleReason: string; cancellationReason: string } = req.body; }: { bookingId: string; rescheduleReason: string; cancellationReason: string } = req.body;
let userOwner: Awaited<ReturnType<typeof findUserOwnerByUserId>>; let userOwner: Awaited<ReturnType<typeof findUserDataByUserId>>;
try { try {
if (session?.user?.id) { if (session?.user?.id) {
userOwner = await findUserOwnerByUserId(session?.user.id); userOwner = await findUserDataByUserId(session?.user.id);
} else { } else {
return res.status(501); return res.status(501);
} }
@ -76,6 +76,10 @@ const handler = async (
location: true, location: true,
attendees: true, attendees: true,
references: true, references: true,
userId: true,
dynamicEventSlugRef: true,
dynamicGroupSlugRef: true,
destinationCalendar: true,
}, },
rejectOnNotFound: true, rejectOnNotFound: true,
where: { where: {
@ -88,8 +92,10 @@ const handler = async (
}, },
}); });
if (bookingToReschedule && bookingToReschedule.eventTypeId && userOwner) { if (bookingToReschedule && userOwner) {
const event = await prisma.eventType.findFirst({ let event: Partial<EventType> = {};
if (bookingToReschedule.eventTypeId) {
event = await prisma.eventType.findFirst({
select: { select: {
title: true, title: true,
users: true, users: true,
@ -100,6 +106,7 @@ const handler = async (
id: bookingToReschedule.eventTypeId, id: bookingToReschedule.eventTypeId,
}, },
}); });
}
await prisma.booking.update({ await prisma.booking.update({
where: { where: {
id: bookingToReschedule.id, id: bookingToReschedule.id,
@ -136,7 +143,7 @@ const handler = async (
const builder = new CalendarEventBuilder(); const builder = new CalendarEventBuilder();
builder.init({ builder.init({
title: bookingToReschedule.title, title: bookingToReschedule.title,
type: event.title, type: event && event.title ? event.title : bookingToReschedule.title,
startTime: bookingToReschedule.startTime.toISOString(), startTime: bookingToReschedule.startTime.toISOString(),
endTime: bookingToReschedule.endTime.toISOString(), endTime: bookingToReschedule.endTime.toISOString(),
attendees: usersToPeopleType( attendees: usersToPeopleType(
@ -149,9 +156,13 @@ const handler = async (
const director = new CalendarEventDirector(); const director = new CalendarEventDirector();
director.setBuilder(builder); director.setBuilder(builder);
director.setExistingBooking(bookingToReschedule as unknown as Booking); director.setExistingBooking(bookingToReschedule);
director.setCancellationReason(cancellationReason); director.setCancellationReason(cancellationReason);
if (!!event) {
await director.buildWithoutEventTypeForRescheduleEmail();
} else {
await director.buildForRescheduleEmail(); await director.buildForRescheduleEmail();
}
// Handling calendar and videos cancellation // Handling calendar and videos cancellation
// This can set previous time as available, until virtual calendar is done // 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 // Send emails
await sendRequestRescheduleEmail(builder.calendarEvent, { await sendRequestRescheduleEmail(builder.calendarEvent, {
rescheduleLink: builder.rescheduleLink, rescheduleLink: builder.rescheduleLink,

View file

@ -1,4 +1,4 @@
import { Prisma } from "@prisma/client"; import { Prisma, Booking } from "@prisma/client";
import dayjs from "dayjs"; import dayjs from "dayjs";
import short from "short-uuid"; import short from "short-uuid";
import { v5 as uuidv5 } from "uuid"; import { v5 as uuidv5 } from "uuid";
@ -80,7 +80,7 @@ export class CalendarEventBuilder implements ICalendarEventBuilder {
} }
users.push(eventTypeUser); users.push(eventTypeUser);
} }
this.users = users; this.setUsers(users);
} }
public buildAttendeesList() { public buildAttendeesList() {
@ -273,17 +273,54 @@ export class CalendarEventBuilder implements ICalendarEventBuilder {
this.calendarEvent.cancellationReason = cancellationReason; this.calendarEvent.cancellationReason = cancellationReason;
} }
public buildRescheduleLink(originalBookingUId: string) { public setUsers(users: User[]) {
if (!this.eventType) { this.users = users;
throw new Error("Run buildEventObjectFromInnerClass before this function"); }
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<Booking>, 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 isTeam = !!this.eventType.teamId;
const queryParams = new URLSearchParams(); const queryParams = new URLSearchParams();
queryParams.set("rescheduleUid", `${originalBookingUId}`); queryParams.set("rescheduleUid", `${booking.uid}`);
const rescheduleLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/${ slug = `${slug}?${queryParams.toString()}`;
isTeam ? `/team/${this.eventType.team?.slug}` : this.users[0].username
}/${this.eventType.slug}?${queryParams.toString()}`; const rescheduleLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/${slug}`;
this.rescheduleLink = rescheduleLink; this.rescheduleLink = rescheduleLink;
} catch (error) {
if (error instanceof Error) {
throw new Error(`buildRescheduleLink.error: ${error.message}`);
}
}
} }
} }

View file

@ -11,7 +11,21 @@ export class CalendarEventDirector {
this.builder = builder; 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; this.existingBooking = booking;
} }
@ -29,9 +43,23 @@ export class CalendarEventDirector {
this.builder.setCancellationReason(this.cancellationReason); this.builder.setCancellationReason(this.cancellationReason);
this.builder.setDescription(this.builder.eventType.description); this.builder.setDescription(this.builder.eventType.description);
this.builder.setNotes(this.existingBooking.description); this.builder.setNotes(this.existingBooking.description);
this.builder.buildRescheduleLink(this.existingBooking.uid); this.builder.buildRescheduleLink(this.existingBooking, this.builder.eventType);
} else { } else {
throw new Error("buildForRescheduleEmail.missing.params.required"); 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");
}
}
} }