From c1bcfcfa3ddf989e5c99609202a81ac7a0522918 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Sat, 26 Aug 2023 01:43:10 +0100 Subject: [PATCH] fix: replace all async foreach callbacks (#10157) Co-authored-by: zomars --- apps/web/playwright/booking-pages.e2e.ts | 3 +- packages/app-store/vital/lib/reschedule.ts | 23 +- .../wipemycalother/lib/reschedule.ts | 22 +- .../app-store/zapier/lib/nodeScheduler.ts | 56 +-- packages/atoms/build.mjs | 3 +- .../bookings/lib/handleCancelBooking.ts | 22 +- .../bookings/requestReschedule.handler.ts | 327 +++++++++--------- .../viewer/workflows/update.handler.ts | 50 +-- 8 files changed, 266 insertions(+), 240 deletions(-) diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index 8a2814c443..3ca81bc2c7 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -158,9 +158,10 @@ test.describe("pro user", () => { 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 Promise.all(promises); }); test("Time slots should be reserved when selected", async ({ context, page }) => { diff --git a/packages/app-store/vital/lib/reschedule.ts b/packages/app-store/vital/lib/reschedule.ts index fdfa3a3b15..53f91ccd32 100644 --- a/packages/app-store/vital/lib/reschedule.ts +++ b/packages/app-store/vital/lib/reschedule.ts @@ -121,18 +121,21 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter( (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 { - 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); - } else if (bookingRef.type.endsWith("_video")) { - return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid); - } - } - }); + await Promise.all(promises); } catch (error) { + // FIXME: error logging - non-Error type errors are currently discarded if (error instanceof Error) { logger.error(error.message); } diff --git a/packages/app-store/wipemycalother/lib/reschedule.ts b/packages/app-store/wipemycalother/lib/reschedule.ts index 2cab855fd1..a9134ffb53 100644 --- a/packages/app-store/wipemycalother/lib/reschedule.ts +++ b/packages/app-store/wipemycalother/lib/reschedule.ts @@ -121,17 +121,19 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter( (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 { - 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); - } else if (bookingRef.type.endsWith("_video")) { - return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid); - } - } - }); + await Promise.all(promises); } catch (error) { if (error instanceof Error) { logger.error(error.message); diff --git a/packages/app-store/zapier/lib/nodeScheduler.ts b/packages/app-store/zapier/lib/nodeScheduler.ts index 7daa26d9aa..e6f7b16436 100644 --- a/packages/app-store/zapier/lib/nodeScheduler.ts +++ b/packages/app-store/zapier/lib/nodeScheduler.ts @@ -10,6 +10,7 @@ export async function scheduleTrigger( ) { try { //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( `${subscriber.appId}_${subscriber.id}`, booking.endTime, @@ -57,38 +58,39 @@ export async function cancelScheduledJobs( appId?: string | null, isReschedule?: boolean ) { - try { - let scheduledJobs = booking.scheduledJobs || []; + if (!booking.scheduledJobs) return; - if (booking.scheduledJobs) { - booking.scheduledJobs.forEach(async (scheduledJob) => { - if (appId) { - if (scheduledJob.startsWith(appId)) { - if (schedule.scheduledJobs[scheduledJob]) { - 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 = []; + let scheduledJobs = booking.scheduledJobs || []; + const promises = booking.scheduledJobs.map(async (scheduledJob) => { + if (appId) { + if (scheduledJob.startsWith(appId)) { + if (schedule.scheduledJobs[scheduledJob]) { + 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 = []; + } - if (!isReschedule) { - await prisma.booking.update({ - where: { - uid: booking.uid, - }, - data: { - scheduledJobs: scheduledJobs, - }, - }); - } + if (!isReschedule) { + await prisma.booking.update({ + where: { + uid: booking.uid, + }, + data: { + scheduledJobs: scheduledJobs, + }, }); } + }); + + try { + await Promise.all(promises); } catch (error) { console.error("Error cancelling scheduled jobs", error); } diff --git a/packages/atoms/build.mjs b/packages/atoms/build.mjs index bd5c138cf6..c5db37a1b7 100644 --- a/packages/atoms/build.mjs +++ b/packages/atoms/build.mjs @@ -12,7 +12,7 @@ const libraries = [ }, ]; -libraries.forEach(async (lib) => { +const promises = libraries.map(async (lib) => { await build({ build: { outDir: `./dist/${lib.fileName}`, @@ -29,3 +29,4 @@ libraries.forEach(async (lib) => { }, }); }); +await Promise.all(promises); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index a8bc147f7e..277aaae159 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -430,9 +430,9 @@ async function handler(req: CustomRequest) { bookingToDelete.recurringEventId && allRemainingBookings ) { - bookingToDelete.user.credentials + const promises = bookingToDelete.user.credentials .filter((credential) => credential.type.endsWith("_calendar")) - .forEach(async (credential) => { + .map(async (credential) => { const calendar = await getCalendar(credential); for (const updBooking of updatedBookings) { 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 { apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise); } @@ -603,11 +610,13 @@ async function handler(req: CustomRequest) { }); // delete scheduled jobs of cancelled bookings + // FIXME: async calls into ether updatedBookings.forEach((booking) => { cancelScheduledJobs(booking); }); //Workflows - cancel all reminders for cancelled bookings + // FIXME: async calls into ether updatedBookings.forEach((booking) => { booking.workflowReminders.forEach((reminder) => { if (reminder.method === WorkflowMethods.EMAIL) { @@ -622,11 +631,14 @@ async function handler(req: CustomRequest) { const prismaPromises: Promise[] = [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 { - 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 }); } catch (error) { console.error("Error deleting event", error); diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index ecf1c16898..324ed23057 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -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" }); } - if (bookingToReschedule) { - let event: Partial = {}; - if (bookingToReschedule.eventTypeId) { - event = await prisma.eventType.findFirstOrThrow({ - select: { - title: true, - users: true, - schedulingType: true, - recurringEvent: true, - }, - where: { - id: bookingToReschedule.eventTypeId, - }, - }); - } - await prisma.booking.update({ + if (!bookingToReschedule) return; + + let event: Partial = {}; + if (bookingToReschedule.eventTypeId) { + event = await prisma.eventType.findFirstOrThrow({ + select: { + title: true, + users: true, + schedulingType: true, + recurringEvent: true, + }, where: { - id: bookingToReschedule.id, - }, - data: { - rescheduled: true, - cancellationReason, - status: BookingStatus.CANCELLED, - updatedAt: dayjs().toISOString(), + id: bookingToReschedule.eventTypeId, }, }); - - // 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); }; diff --git a/packages/trpc/server/routers/viewer/workflows/update.handler.ts b/packages/trpc/server/routers/viewer/workflows/update.handler.ts index a138030317..36749eca0d 100644 --- a/packages/trpc/server/routers/viewer/workflows/update.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/update.handler.ts @@ -264,13 +264,13 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }, }); - steps.forEach(async (step) => { + const promiseSteps = steps.map(async (step) => { if ( step.action !== WorkflowActions.SMS_ATTENDEE && step.action !== WorkflowActions.WHATSAPP_ATTENDEE ) { //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 bookingInfo = { 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 - activeOnEventTypes.forEach(async (eventType) => { - await ctx.prisma.workflowsOnEventTypes.createMany({ - data: { - workflowId: 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 ctx.prisma.workflowsOnEventTypes.createMany({ + data: activeOnEventTypes.map((eventType) => ({ + workflowId: id, + eventTypeId: eventType.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) => { @@ -465,11 +464,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }); //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) { 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 eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => { @@ -497,7 +499,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { user: true, }, }); - bookingsOfEventTypes.forEach(async (booking) => { + const promiseScheduleReminders = bookingsOfEventTypes.map(async (booking) => { const defaultLocale = "en"; const bookingInfo = { 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; } }); - addedSteps.forEach(async (step) => { + const promiseAddedSteps = addedSteps.map(async (step) => { if (step) { const { senderName, ...newStep } = step; newStep.sender = getSender({ @@ -749,6 +752,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { } } }); + await Promise.all(promiseAddedSteps); } //update trigger, name, time, timeUnit