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 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,
|
||||||
|
|
|
@ -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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue