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 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<ReturnType<typeof findUserOwnerByUserId>>;
let userOwner: Awaited<ReturnType<typeof findUserDataByUserId>>;
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<EventType> = {};
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,

View file

@ -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<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 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}`);
}
}
}
}

View file

@ -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");
}
}
}