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:
parent
6ec2b20a23
commit
df7abdfc06
3 changed files with 135 additions and 34 deletions
|
@ -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,
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue