fix: replace all async foreach callbacks (#10157)
Co-authored-by: zomars <zomars@me.com>fix/storybook-builds^2
parent
83aea7d28c
commit
c1bcfcfa3d
|
@ -158,9 +158,10 @@ test.describe("pro user", () => {
|
||||||
|
|
||||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||||
|
|
||||||
additionalGuests.forEach(async (email) => {
|
const promises = additionalGuests.map(async (email) => {
|
||||||
await expect(page.locator(`[data-testid="attendee-email-${email}"]`)).toHaveText(email);
|
await expect(page.locator(`[data-testid="attendee-email-${email}"]`)).toHaveText(email);
|
||||||
});
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Time slots should be reserved when selected", async ({ context, page }) => {
|
test("Time slots should be reserved when selected", async ({ context, page }) => {
|
||||||
|
|
|
@ -121,18 +121,21 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
|
||||||
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
|
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
|
||||||
(ref) => !!credentialsMap.get(ref.type)
|
(ref) => !!credentialsMap.get(ref.type)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const promises = bookingRefsFiltered.map(async (bookingRef) => {
|
||||||
|
if (!bookingRef.uid) return;
|
||||||
|
|
||||||
|
if (bookingRef.type.endsWith("_calendar")) {
|
||||||
|
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
||||||
|
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
|
||||||
|
} else if (bookingRef.type.endsWith("_video")) {
|
||||||
|
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
||||||
|
}
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
bookingRefsFiltered.forEach(async (bookingRef) => {
|
await Promise.all(promises);
|
||||||
if (bookingRef.uid) {
|
|
||||||
if (bookingRef.type.endsWith("_calendar")) {
|
|
||||||
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
|
||||||
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
|
|
||||||
} else if (bookingRef.type.endsWith("_video")) {
|
|
||||||
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// FIXME: error logging - non-Error type errors are currently discarded
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
logger.error(error.message);
|
logger.error(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,17 +121,19 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
|
||||||
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
|
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
|
||||||
(ref) => !!credentialsMap.get(ref.type)
|
(ref) => !!credentialsMap.get(ref.type)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const promises = bookingRefsFiltered.map(async (bookingRef) => {
|
||||||
|
if (!bookingRef.uid) return;
|
||||||
|
|
||||||
|
if (bookingRef.type.endsWith("_calendar")) {
|
||||||
|
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
||||||
|
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
|
||||||
|
} else if (bookingRef.type.endsWith("_video")) {
|
||||||
|
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
||||||
|
}
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
bookingRefsFiltered.forEach(async (bookingRef) => {
|
await Promise.all(promises);
|
||||||
if (bookingRef.uid) {
|
|
||||||
if (bookingRef.type.endsWith("_calendar")) {
|
|
||||||
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
|
||||||
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
|
|
||||||
} else if (bookingRef.type.endsWith("_video")) {
|
|
||||||
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
logger.error(error.message);
|
logger.error(error.message);
|
||||||
|
|
|
@ -10,6 +10,7 @@ export async function scheduleTrigger(
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
//schedule job to call subscriber url at the end of meeting
|
//schedule job to call subscriber url at the end of meeting
|
||||||
|
// FIXME: in-process scheduling - job will vanish on server crash / restart
|
||||||
const job = schedule.scheduleJob(
|
const job = schedule.scheduleJob(
|
||||||
`${subscriber.appId}_${subscriber.id}`,
|
`${subscriber.appId}_${subscriber.id}`,
|
||||||
booking.endTime,
|
booking.endTime,
|
||||||
|
@ -57,38 +58,39 @@ export async function cancelScheduledJobs(
|
||||||
appId?: string | null,
|
appId?: string | null,
|
||||||
isReschedule?: boolean
|
isReschedule?: boolean
|
||||||
) {
|
) {
|
||||||
try {
|
if (!booking.scheduledJobs) return;
|
||||||
let scheduledJobs = booking.scheduledJobs || [];
|
|
||||||
|
|
||||||
if (booking.scheduledJobs) {
|
let scheduledJobs = booking.scheduledJobs || [];
|
||||||
booking.scheduledJobs.forEach(async (scheduledJob) => {
|
const promises = booking.scheduledJobs.map(async (scheduledJob) => {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
if (scheduledJob.startsWith(appId)) {
|
if (scheduledJob.startsWith(appId)) {
|
||||||
if (schedule.scheduledJobs[scheduledJob]) {
|
if (schedule.scheduledJobs[scheduledJob]) {
|
||||||
schedule.scheduledJobs[scheduledJob].cancel();
|
schedule.scheduledJobs[scheduledJob].cancel();
|
||||||
}
|
|
||||||
scheduledJobs = scheduledJobs?.filter((job) => scheduledJob !== job) || [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//if no specific appId given, delete all scheduled jobs of booking
|
|
||||||
if (schedule.scheduledJobs[scheduledJob]) {
|
|
||||||
schedule.scheduledJobs[scheduledJob].cancel();
|
|
||||||
}
|
|
||||||
scheduledJobs = [];
|
|
||||||
}
|
}
|
||||||
|
scheduledJobs = scheduledJobs?.filter((job) => scheduledJob !== job) || [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//if no specific appId given, delete all scheduled jobs of booking
|
||||||
|
if (schedule.scheduledJobs[scheduledJob]) {
|
||||||
|
schedule.scheduledJobs[scheduledJob].cancel();
|
||||||
|
}
|
||||||
|
scheduledJobs = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (!isReschedule) {
|
if (!isReschedule) {
|
||||||
await prisma.booking.update({
|
await prisma.booking.update({
|
||||||
where: {
|
where: {
|
||||||
uid: booking.uid,
|
uid: booking.uid,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
scheduledJobs: scheduledJobs,
|
scheduledJobs: scheduledJobs,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error cancelling scheduled jobs", error);
|
console.error("Error cancelling scheduled jobs", error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ const libraries = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
libraries.forEach(async (lib) => {
|
const promises = libraries.map(async (lib) => {
|
||||||
await build({
|
await build({
|
||||||
build: {
|
build: {
|
||||||
outDir: `./dist/${lib.fileName}`,
|
outDir: `./dist/${lib.fileName}`,
|
||||||
|
@ -29,3 +29,4 @@ libraries.forEach(async (lib) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
|
@ -430,9 +430,9 @@ async function handler(req: CustomRequest) {
|
||||||
bookingToDelete.recurringEventId &&
|
bookingToDelete.recurringEventId &&
|
||||||
allRemainingBookings
|
allRemainingBookings
|
||||||
) {
|
) {
|
||||||
bookingToDelete.user.credentials
|
const promises = bookingToDelete.user.credentials
|
||||||
.filter((credential) => credential.type.endsWith("_calendar"))
|
.filter((credential) => credential.type.endsWith("_calendar"))
|
||||||
.forEach(async (credential) => {
|
.map(async (credential) => {
|
||||||
const calendar = await getCalendar(credential);
|
const calendar = await getCalendar(credential);
|
||||||
for (const updBooking of updatedBookings) {
|
for (const updBooking of updatedBookings) {
|
||||||
const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
|
const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
|
||||||
|
@ -443,6 +443,13 @@ async function handler(req: CustomRequest) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
|
apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
|
||||||
}
|
}
|
||||||
|
@ -603,11 +610,13 @@ async function handler(req: CustomRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete scheduled jobs of cancelled bookings
|
// delete scheduled jobs of cancelled bookings
|
||||||
|
// FIXME: async calls into ether
|
||||||
updatedBookings.forEach((booking) => {
|
updatedBookings.forEach((booking) => {
|
||||||
cancelScheduledJobs(booking);
|
cancelScheduledJobs(booking);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Workflows - cancel all reminders for cancelled bookings
|
//Workflows - cancel all reminders for cancelled bookings
|
||||||
|
// FIXME: async calls into ether
|
||||||
updatedBookings.forEach((booking) => {
|
updatedBookings.forEach((booking) => {
|
||||||
booking.workflowReminders.forEach((reminder) => {
|
booking.workflowReminders.forEach((reminder) => {
|
||||||
if (reminder.method === WorkflowMethods.EMAIL) {
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
||||||
|
@ -622,11 +631,14 @@ async function handler(req: CustomRequest) {
|
||||||
|
|
||||||
const prismaPromises: Promise<unknown>[] = [bookingReferenceDeletes];
|
const prismaPromises: Promise<unknown>[] = [bookingReferenceDeletes];
|
||||||
|
|
||||||
// @TODO: find a way in the future if a promise fails don't stop the rest of the promises
|
|
||||||
// Also if emails fails try to requeue them
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(prismaPromises.concat(apiDeletes));
|
const settled = await Promise.allSettled(prismaPromises.concat(apiDeletes));
|
||||||
|
const rejected = settled.filter(({ status }) => status === "rejected") as PromiseRejectedResult[];
|
||||||
|
if (rejected.length) {
|
||||||
|
throw new Error(`Reasons: ${rejected.map(({ reason }) => reason)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: if emails fail try to requeue them
|
||||||
await sendCancelledEmails(evt, { eventName: bookingToDelete?.eventType?.eventName });
|
await sendCancelledEmails(evt, { eventName: bookingToDelete?.eventType?.eventName });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting event", error);
|
console.error("Error deleting event", error);
|
||||||
|
|
|
@ -102,171 +102,172 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
|
||||||
throw new TRPCError({ code: "FORBIDDEN", message: "User isn't owner of the current booking" });
|
throw new TRPCError({ code: "FORBIDDEN", message: "User isn't owner of the current booking" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookingToReschedule) {
|
if (!bookingToReschedule) return;
|
||||||
let event: Partial<EventType> = {};
|
|
||||||
if (bookingToReschedule.eventTypeId) {
|
let event: Partial<EventType> = {};
|
||||||
event = await prisma.eventType.findFirstOrThrow({
|
if (bookingToReschedule.eventTypeId) {
|
||||||
select: {
|
event = await prisma.eventType.findFirstOrThrow({
|
||||||
title: true,
|
select: {
|
||||||
users: true,
|
title: true,
|
||||||
schedulingType: true,
|
users: true,
|
||||||
recurringEvent: true,
|
schedulingType: true,
|
||||||
},
|
recurringEvent: true,
|
||||||
where: {
|
},
|
||||||
id: bookingToReschedule.eventTypeId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await prisma.booking.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: bookingToReschedule.id,
|
id: bookingToReschedule.eventTypeId,
|
||||||
},
|
|
||||||
data: {
|
|
||||||
rescheduled: true,
|
|
||||||
cancellationReason,
|
|
||||||
status: BookingStatus.CANCELLED,
|
|
||||||
updatedAt: dayjs().toISOString(),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete scheduled jobs of previous booking
|
|
||||||
cancelScheduledJobs(bookingToReschedule);
|
|
||||||
|
|
||||||
//cancel workflow reminders of previous booking
|
|
||||||
bookingToReschedule.workflowReminders.forEach((reminder) => {
|
|
||||||
if (reminder.method === WorkflowMethods.EMAIL) {
|
|
||||||
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
|
||||||
} else if (reminder.method === WorkflowMethods.SMS) {
|
|
||||||
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
|
||||||
} else if (reminder.method === WorkflowMethods.WHATSAPP) {
|
|
||||||
deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const [mainAttendee] = bookingToReschedule.attendees;
|
|
||||||
// @NOTE: Should we assume attendees language?
|
|
||||||
const tAttendees = await getTranslation(mainAttendee.locale ?? "en", "common");
|
|
||||||
const usersToPeopleType = (
|
|
||||||
users: PersonAttendeeCommonFields[],
|
|
||||||
selectedLanguage: TFunction
|
|
||||||
): Person[] => {
|
|
||||||
return users?.map((user) => {
|
|
||||||
return {
|
|
||||||
email: user.email || "",
|
|
||||||
name: user.name || "",
|
|
||||||
username: user?.username || "",
|
|
||||||
language: { translate: selectedLanguage, locale: user.locale || "en" },
|
|
||||||
timeZone: user?.timeZone,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const userTranslation = await getTranslation(user.locale ?? "en", "common");
|
|
||||||
const [userAsPeopleType] = usersToPeopleType([user], userTranslation);
|
|
||||||
|
|
||||||
const builder = new CalendarEventBuilder();
|
|
||||||
builder.init({
|
|
||||||
title: bookingToReschedule.title,
|
|
||||||
type: event && event.title ? event.title : bookingToReschedule.title,
|
|
||||||
startTime: bookingToReschedule.startTime.toISOString(),
|
|
||||||
endTime: bookingToReschedule.endTime.toISOString(),
|
|
||||||
attendees: usersToPeopleType(
|
|
||||||
// username field doesn't exists on attendee but could be in the future
|
|
||||||
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
|
|
||||||
tAttendees
|
|
||||||
),
|
|
||||||
organizer: userAsPeopleType,
|
|
||||||
});
|
|
||||||
|
|
||||||
const director = new CalendarEventDirector();
|
|
||||||
director.setBuilder(builder);
|
|
||||||
director.setExistingBooking(bookingToReschedule);
|
|
||||||
cancellationReason && director.setCancellationReason(cancellationReason);
|
|
||||||
if (event) {
|
|
||||||
await director.buildForRescheduleEmail();
|
|
||||||
} else {
|
|
||||||
await director.buildWithoutEventTypeForRescheduleEmail();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handling calendar and videos cancellation
|
|
||||||
// This can set previous time as available, until virtual calendar is done
|
|
||||||
const credentials = await getUsersCredentials(user.id);
|
|
||||||
const credentialsMap = new Map();
|
|
||||||
credentials.forEach((credential) => {
|
|
||||||
credentialsMap.set(credential.type, credential);
|
|
||||||
});
|
|
||||||
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter((ref) =>
|
|
||||||
credentialsMap.has(ref.type)
|
|
||||||
);
|
|
||||||
bookingRefsFiltered.forEach(async (bookingRef) => {
|
|
||||||
if (bookingRef.uid) {
|
|
||||||
if (bookingRef.type.endsWith("_calendar")) {
|
|
||||||
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
|
||||||
|
|
||||||
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent, bookingRef.externalCalendarId);
|
|
||||||
} else if (bookingRef.type.endsWith("_video")) {
|
|
||||||
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send emails
|
|
||||||
await sendRequestRescheduleEmail(builder.calendarEvent, {
|
|
||||||
rescheduleLink: builder.rescheduleLink,
|
|
||||||
});
|
|
||||||
|
|
||||||
const evt: CalendarEvent = {
|
|
||||||
title: bookingToReschedule?.title,
|
|
||||||
type: event && event.title ? event.title : bookingToReschedule.title,
|
|
||||||
description: bookingToReschedule?.description || "",
|
|
||||||
customInputs: isPrismaObjOrUndefined(bookingToReschedule.customInputs),
|
|
||||||
...getCalEventResponses({
|
|
||||||
booking: bookingToReschedule,
|
|
||||||
bookingFields: bookingToReschedule.eventType?.bookingFields ?? null,
|
|
||||||
}),
|
|
||||||
startTime: bookingToReschedule?.startTime ? dayjs(bookingToReschedule.startTime).format() : "",
|
|
||||||
endTime: bookingToReschedule?.endTime ? dayjs(bookingToReschedule.endTime).format() : "",
|
|
||||||
organizer: userAsPeopleType,
|
|
||||||
attendees: usersToPeopleType(
|
|
||||||
// username field doesn't exists on attendee but could be in the future
|
|
||||||
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
|
|
||||||
tAttendees
|
|
||||||
),
|
|
||||||
uid: bookingToReschedule?.uid,
|
|
||||||
location: bookingToReschedule?.location,
|
|
||||||
destinationCalendar:
|
|
||||||
bookingToReschedule?.destinationCalendar || bookingToReschedule?.destinationCalendar,
|
|
||||||
cancellationReason: `Please reschedule. ${cancellationReason}`, // TODO::Add i18-next for this
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send webhook
|
|
||||||
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
|
|
||||||
|
|
||||||
const teamId = await getTeamIdFromEventType({
|
|
||||||
eventType: {
|
|
||||||
team: { id: bookingToReschedule.eventType?.teamId ?? null },
|
|
||||||
parentId: bookingToReschedule?.eventType?.parentId ?? null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerForUser = !teamId || (teamId && bookingToReschedule.eventType?.parentId);
|
|
||||||
|
|
||||||
// Send Webhook call if hooked to BOOKING.CANCELLED
|
|
||||||
const subscriberOptions = {
|
|
||||||
userId: triggerForUser ? bookingToReschedule.userId : null,
|
|
||||||
eventTypeId: bookingToReschedule.eventTypeId as number,
|
|
||||||
triggerEvent: eventTrigger,
|
|
||||||
teamId,
|
|
||||||
};
|
|
||||||
const webhooks = await getWebhooks(subscriberOptions);
|
|
||||||
const promises = webhooks.map((webhook) =>
|
|
||||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
|
||||||
...evt,
|
|
||||||
smsReminderNumber: bookingToReschedule.smsReminderNumber || undefined,
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
await prisma.booking.update({
|
||||||
|
where: {
|
||||||
|
id: bookingToReschedule.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
rescheduled: true,
|
||||||
|
cancellationReason,
|
||||||
|
status: BookingStatus.CANCELLED,
|
||||||
|
updatedAt: dayjs().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete scheduled jobs of previous booking
|
||||||
|
// FIXME: async fn off into the ether
|
||||||
|
cancelScheduledJobs(bookingToReschedule);
|
||||||
|
|
||||||
|
//cancel workflow reminders of previous booking
|
||||||
|
// FIXME: more async fns off into the ether
|
||||||
|
bookingToReschedule.workflowReminders.forEach((reminder) => {
|
||||||
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
||||||
|
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
||||||
|
} else if (reminder.method === WorkflowMethods.SMS) {
|
||||||
|
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
||||||
|
} else if (reminder.method === WorkflowMethods.WHATSAPP) {
|
||||||
|
deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [mainAttendee] = bookingToReschedule.attendees;
|
||||||
|
// @NOTE: Should we assume attendees language?
|
||||||
|
const tAttendees = await getTranslation(mainAttendee.locale ?? "en", "common");
|
||||||
|
const usersToPeopleType = (users: PersonAttendeeCommonFields[], selectedLanguage: TFunction): Person[] => {
|
||||||
|
return users?.map((user) => {
|
||||||
|
return {
|
||||||
|
email: user.email || "",
|
||||||
|
name: user.name || "",
|
||||||
|
username: user?.username || "",
|
||||||
|
language: { translate: selectedLanguage, locale: user.locale || "en" },
|
||||||
|
timeZone: user?.timeZone,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const userTranslation = await getTranslation(user.locale ?? "en", "common");
|
||||||
|
const [userAsPeopleType] = usersToPeopleType([user], userTranslation);
|
||||||
|
|
||||||
|
const builder = new CalendarEventBuilder();
|
||||||
|
builder.init({
|
||||||
|
title: bookingToReschedule.title,
|
||||||
|
type: event && event.title ? event.title : bookingToReschedule.title,
|
||||||
|
startTime: bookingToReschedule.startTime.toISOString(),
|
||||||
|
endTime: bookingToReschedule.endTime.toISOString(),
|
||||||
|
attendees: usersToPeopleType(
|
||||||
|
// username field doesn't exists on attendee but could be in the future
|
||||||
|
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
|
||||||
|
tAttendees
|
||||||
|
),
|
||||||
|
organizer: userAsPeopleType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const director = new CalendarEventDirector();
|
||||||
|
director.setBuilder(builder);
|
||||||
|
director.setExistingBooking(bookingToReschedule);
|
||||||
|
cancellationReason && director.setCancellationReason(cancellationReason);
|
||||||
|
if (event) {
|
||||||
|
await director.buildForRescheduleEmail();
|
||||||
|
} else {
|
||||||
|
await director.buildWithoutEventTypeForRescheduleEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling calendar and videos cancellation
|
||||||
|
// This can set previous time as available, until virtual calendar is done
|
||||||
|
const credentials = await getUsersCredentials(user.id);
|
||||||
|
const credentialsMap = new Map();
|
||||||
|
credentials.forEach((credential) => {
|
||||||
|
credentialsMap.set(credential.type, credential);
|
||||||
|
});
|
||||||
|
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter((ref) =>
|
||||||
|
credentialsMap.has(ref.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME: error-handling
|
||||||
|
await Promise.allSettled(
|
||||||
|
bookingRefsFiltered.map(async (bookingRef) => {
|
||||||
|
if (!bookingRef.uid) return;
|
||||||
|
|
||||||
|
if (bookingRef.type.endsWith("_calendar")) {
|
||||||
|
const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
|
||||||
|
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent, bookingRef.externalCalendarId);
|
||||||
|
} else if (bookingRef.type.endsWith("_video")) {
|
||||||
|
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send emails
|
||||||
|
await sendRequestRescheduleEmail(builder.calendarEvent, {
|
||||||
|
rescheduleLink: builder.rescheduleLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
const evt: CalendarEvent = {
|
||||||
|
title: bookingToReschedule?.title,
|
||||||
|
type: event && event.title ? event.title : bookingToReschedule.title,
|
||||||
|
description: bookingToReschedule?.description || "",
|
||||||
|
customInputs: isPrismaObjOrUndefined(bookingToReschedule.customInputs),
|
||||||
|
...getCalEventResponses({
|
||||||
|
booking: bookingToReschedule,
|
||||||
|
bookingFields: bookingToReschedule.eventType?.bookingFields ?? null,
|
||||||
|
}),
|
||||||
|
startTime: bookingToReschedule?.startTime ? dayjs(bookingToReschedule.startTime).format() : "",
|
||||||
|
endTime: bookingToReschedule?.endTime ? dayjs(bookingToReschedule.endTime).format() : "",
|
||||||
|
organizer: userAsPeopleType,
|
||||||
|
attendees: usersToPeopleType(
|
||||||
|
// username field doesn't exists on attendee but could be in the future
|
||||||
|
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
|
||||||
|
tAttendees
|
||||||
|
),
|
||||||
|
uid: bookingToReschedule?.uid,
|
||||||
|
location: bookingToReschedule?.location,
|
||||||
|
destinationCalendar: bookingToReschedule?.destinationCalendar || bookingToReschedule?.destinationCalendar,
|
||||||
|
cancellationReason: `Please reschedule. ${cancellationReason}`, // TODO::Add i18-next for this
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send webhook
|
||||||
|
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
|
||||||
|
|
||||||
|
const teamId = await getTeamIdFromEventType({
|
||||||
|
eventType: {
|
||||||
|
team: { id: bookingToReschedule.eventType?.teamId ?? null },
|
||||||
|
parentId: bookingToReschedule?.eventType?.parentId ?? null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerForUser = !teamId || (teamId && bookingToReschedule.eventType?.parentId);
|
||||||
|
|
||||||
|
// Send Webhook call if hooked to BOOKING.CANCELLED
|
||||||
|
const subscriberOptions = {
|
||||||
|
userId: triggerForUser ? bookingToReschedule.userId : null,
|
||||||
|
eventTypeId: bookingToReschedule.eventTypeId as number,
|
||||||
|
triggerEvent: eventTrigger,
|
||||||
|
teamId,
|
||||||
|
};
|
||||||
|
const webhooks = await getWebhooks(subscriberOptions);
|
||||||
|
const promises = webhooks.map((webhook) =>
|
||||||
|
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||||
|
...evt,
|
||||||
|
smsReminderNumber: bookingToReschedule.smsReminderNumber || undefined,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
|
@ -264,13 +264,13 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
steps.forEach(async (step) => {
|
const promiseSteps = steps.map(async (step) => {
|
||||||
if (
|
if (
|
||||||
step.action !== WorkflowActions.SMS_ATTENDEE &&
|
step.action !== WorkflowActions.SMS_ATTENDEE &&
|
||||||
step.action !== WorkflowActions.WHATSAPP_ATTENDEE
|
step.action !== WorkflowActions.WHATSAPP_ATTENDEE
|
||||||
) {
|
) {
|
||||||
//as we do not have attendees phone number (user is notified about that when setting this action)
|
//as we do not have attendees phone number (user is notified about that when setting this action)
|
||||||
bookingsForReminders.forEach(async (booking) => {
|
const promiseScheduleReminders = bookingsForReminders.map(async (booking) => {
|
||||||
const defaultLocale = "en";
|
const defaultLocale = "en";
|
||||||
const bookingInfo = {
|
const bookingInfo = {
|
||||||
uid: booking.uid,
|
uid: booking.uid,
|
||||||
|
@ -367,29 +367,28 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await Promise.all(promiseScheduleReminders);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await Promise.all(promiseSteps);
|
||||||
}
|
}
|
||||||
//create all workflow - eventtypes relationships
|
//create all workflow - eventtypes relationships
|
||||||
activeOnEventTypes.forEach(async (eventType) => {
|
await ctx.prisma.workflowsOnEventTypes.createMany({
|
||||||
await ctx.prisma.workflowsOnEventTypes.createMany({
|
data: activeOnEventTypes.map((eventType) => ({
|
||||||
data: {
|
workflowId: id,
|
||||||
workflowId: id,
|
eventTypeId: eventType.id,
|
||||||
eventTypeId: eventType.id,
|
})),
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (eventType.children.length) {
|
|
||||||
eventType.children.forEach(async (chEventType) => {
|
|
||||||
await ctx.prisma.workflowsOnEventTypes.createMany({
|
|
||||||
data: {
|
|
||||||
workflowId: id,
|
|
||||||
eventTypeId: chEventType.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
await Promise.all(
|
||||||
|
activeOnEventTypes.map((eventType) =>
|
||||||
|
ctx.prisma.workflowsOnEventTypes.createMany({
|
||||||
|
data: eventType.children.map((chEventType) => ({
|
||||||
|
workflowId: id,
|
||||||
|
eventTypeId: chEventType.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
userWorkflow.steps.map(async (oldStep) => {
|
userWorkflow.steps.map(async (oldStep) => {
|
||||||
|
@ -465,11 +464,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
//cancel all workflow reminders from steps that were edited
|
//cancel all workflow reminders from steps that were edited
|
||||||
remindersToUpdate.forEach(async (reminder) => {
|
// FIXME: async calls into ether
|
||||||
|
remindersToUpdate.forEach((reminder) => {
|
||||||
if (reminder.method === WorkflowMethods.EMAIL) {
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
||||||
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
||||||
} else if (reminder.method === WorkflowMethods.SMS) {
|
} else if (reminder.method === WorkflowMethods.SMS) {
|
||||||
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
||||||
|
} else if (reminder.method === WorkflowMethods.WHATSAPP) {
|
||||||
|
deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => {
|
const eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => {
|
||||||
|
@ -497,7 +499,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
bookingsOfEventTypes.forEach(async (booking) => {
|
const promiseScheduleReminders = bookingsOfEventTypes.map(async (booking) => {
|
||||||
const defaultLocale = "en";
|
const defaultLocale = "en";
|
||||||
const bookingInfo = {
|
const bookingInfo = {
|
||||||
uid: booking.uid,
|
uid: booking.uid,
|
||||||
|
@ -594,6 +596,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await Promise.all(promiseScheduleReminders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -617,7 +620,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
return activeEventType;
|
return activeEventType;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addedSteps.forEach(async (step) => {
|
const promiseAddedSteps = addedSteps.map(async (step) => {
|
||||||
if (step) {
|
if (step) {
|
||||||
const { senderName, ...newStep } = step;
|
const { senderName, ...newStep } = step;
|
||||||
newStep.sender = getSender({
|
newStep.sender = getSender({
|
||||||
|
@ -749,6 +752,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await Promise.all(promiseAddedSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update trigger, name, time, timeUnit
|
//update trigger, name, time, timeUnit
|
||||||
|
|
Loading…
Reference in New Issue