fix: unhandled promise rejection in scheduleWorkflowReminder (#12301)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
parent
b2f4eaee63
commit
a2f859b55a
|
@ -6,12 +6,19 @@ import { v4 as uuidv4 } from "uuid";
|
|||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { PartialWorkflowReminder } from "../lib/getWorkflowReminders";
|
||||
import {
|
||||
getAllRemindersToCancel,
|
||||
getAllRemindersToDelete,
|
||||
getAllUnscheduledReminders,
|
||||
} from "../lib/getWorkflowReminders";
|
||||
import { getiCalEventAsString } from "../lib/getiCalEventAsString";
|
||||
import type { VariablesType } from "../lib/reminders/templates/customTemplate";
|
||||
import customTemplate from "../lib/reminders/templates/customTemplate";
|
||||
|
@ -38,45 +45,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
const sandboxMode = process.env.NEXT_PUBLIC_IS_E2E ? true : false;
|
||||
|
||||
// delete batch_ids with already past scheduled date from scheduled_sends
|
||||
const pageSize = 90;
|
||||
let pageNumber = 0;
|
||||
const remindersToDelete: { referenceId: string | null }[] = await getAllRemindersToDelete();
|
||||
|
||||
const deletePromises: Promise<any>[] = [];
|
||||
|
||||
while (true) {
|
||||
const remindersToDelete = await prisma.workflowReminder.findMany({
|
||||
where: {
|
||||
method: WorkflowMethods.EMAIL,
|
||||
cancelled: true,
|
||||
scheduledDate: {
|
||||
lte: dayjs().toISOString(),
|
||||
},
|
||||
},
|
||||
skip: pageNumber * pageSize,
|
||||
take: pageSize,
|
||||
select: {
|
||||
referenceId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (remindersToDelete.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const reminder of remindersToDelete) {
|
||||
const deletePromise = client.request({
|
||||
url: `/v3/user/scheduled_sends/${reminder.referenceId}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
deletePromises.push(deletePromise);
|
||||
}
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
Promise.allSettled(deletePromises).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.log(`Error deleting batch id from scheduled_sends: ${result.reason}`);
|
||||
logger.error(`Error deleting batch id from scheduled_sends: ${result.reason}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -92,30 +76,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
});
|
||||
|
||||
//cancel reminders for cancelled/rescheduled bookings that are scheduled within the next hour
|
||||
pageNumber = 0;
|
||||
const remindersToCancel: { referenceId: string | null; id: number }[] = await getAllRemindersToCancel();
|
||||
|
||||
const allPromisesCancelReminders: Promise<any>[] = [];
|
||||
|
||||
while (true) {
|
||||
const remindersToCancel = await prisma.workflowReminder.findMany({
|
||||
where: {
|
||||
cancelled: true,
|
||||
scheduled: true, //if it is false then they are already cancelled
|
||||
scheduledDate: {
|
||||
lte: dayjs().add(1, "hour").toISOString(),
|
||||
},
|
||||
},
|
||||
skip: pageNumber * pageSize,
|
||||
take: pageSize,
|
||||
select: {
|
||||
referenceId: true,
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (remindersToCancel.length === 0) {
|
||||
break;
|
||||
}
|
||||
const cancelUpdatePromises: Promise<any>[] = [];
|
||||
|
||||
for (const reminder of remindersToCancel) {
|
||||
const cancelPromise = client.request({
|
||||
|
@ -136,93 +99,27 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
},
|
||||
});
|
||||
|
||||
allPromisesCancelReminders.push(cancelPromise, updatePromise);
|
||||
}
|
||||
pageNumber++;
|
||||
cancelUpdatePromises.push(cancelPromise, updatePromise);
|
||||
}
|
||||
|
||||
Promise.allSettled(allPromisesCancelReminders).then((results) => {
|
||||
Promise.allSettled(cancelUpdatePromises).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.log(`Error cancelling scheduled_sends: ${result.reason}`);
|
||||
logger.error(`Error cancelling scheduled_sends: ${result.reason}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// schedule all unscheduled reminders within the next 72 hours
|
||||
pageNumber = 0;
|
||||
const sendEmailPromises: Promise<any>[] = [];
|
||||
|
||||
while (true) {
|
||||
const unscheduledReminders = await prisma.workflowReminder.findMany({
|
||||
where: {
|
||||
method: WorkflowMethods.EMAIL,
|
||||
scheduled: false,
|
||||
scheduledDate: {
|
||||
lte: dayjs().add(72, "hour").toISOString(),
|
||||
},
|
||||
OR: [{ cancelled: false }, { cancelled: null }],
|
||||
},
|
||||
skip: pageNumber * pageSize,
|
||||
take: pageSize,
|
||||
select: {
|
||||
id: true,
|
||||
scheduledDate: true,
|
||||
workflowStep: {
|
||||
select: {
|
||||
action: true,
|
||||
sendTo: true,
|
||||
reminderBody: true,
|
||||
emailSubject: true,
|
||||
template: true,
|
||||
sender: true,
|
||||
includeCalendarEvent: true,
|
||||
},
|
||||
},
|
||||
booking: {
|
||||
select: {
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
description: true,
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
timeZone: true,
|
||||
locale: true,
|
||||
username: true,
|
||||
timeFormat: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
},
|
||||
metadata: true,
|
||||
uid: true,
|
||||
customInputs: true,
|
||||
responses: true,
|
||||
attendees: true,
|
||||
eventType: {
|
||||
select: {
|
||||
bookingFields: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
recurringEvent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const unscheduledReminders: PartialWorkflowReminder[] = await getAllUnscheduledReminders();
|
||||
|
||||
if (!unscheduledReminders.length && pageNumber === 0) {
|
||||
if (!unscheduledReminders.length) {
|
||||
res.status(200).json({ message: "No Emails to schedule" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (unscheduledReminders.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const reminder of unscheduledReminders) {
|
||||
if (!reminder.workflowStep || !reminder.booking) {
|
||||
continue;
|
||||
|
@ -264,9 +161,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
let emailContent = {
|
||||
emailSubject: reminder.workflowStep.emailSubject || "",
|
||||
emailBody: `<body style="white-space: pre-wrap;">${
|
||||
reminder.workflowStep.reminderBody || ""
|
||||
}</body>`,
|
||||
emailBody: `<body style="white-space: pre-wrap;">${reminder.workflowStep.reminderBody || ""}</body>`,
|
||||
};
|
||||
|
||||
let emailBodyEmpty = false;
|
||||
|
@ -383,21 +278,19 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error scheduling Email with error ${error}`);
|
||||
logger.error(`Error scheduling Email with error ${error}`);
|
||||
}
|
||||
}
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
Promise.allSettled(sendEmailPromises).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.log("Email sending failed", result.reason);
|
||||
logger.error("Email sending failed", result.reason);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
res.status(200).json({ message: "Emails scheduled" });
|
||||
res.status(200).json({ message: `${unscheduledReminders.length} Emails scheduled` });
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
import dayjs from "@calcom/dayjs";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { EventType, Prisma, User, WorkflowReminder, WorkflowStep } from "@calcom/prisma/client";
|
||||
import { WorkflowMethods } from "@calcom/prisma/enums";
|
||||
|
||||
type PartialWorkflowStep = Partial<WorkflowStep> | null;
|
||||
|
||||
type Booking = Prisma.BookingGetPayload<{
|
||||
include: {
|
||||
attendees: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
type PartialBooking =
|
||||
| (Pick<
|
||||
Booking,
|
||||
| "startTime"
|
||||
| "endTime"
|
||||
| "location"
|
||||
| "description"
|
||||
| "metadata"
|
||||
| "customInputs"
|
||||
| "responses"
|
||||
| "uid"
|
||||
| "attendees"
|
||||
> & { eventType: Partial<EventType> | null } & { user: Partial<User> | null })
|
||||
| null;
|
||||
|
||||
export type PartialWorkflowReminder = Pick<WorkflowReminder, "id" | "scheduledDate"> & {
|
||||
booking: PartialBooking | null;
|
||||
} & { workflowStep: PartialWorkflowStep };
|
||||
|
||||
async function getWorkflowReminders<T extends Prisma.WorkflowReminderSelect>(
|
||||
filter: Prisma.WorkflowReminderWhereInput,
|
||||
select: T
|
||||
): Promise<Array<Prisma.WorkflowReminderGetPayload<{ select: T }>>> {
|
||||
const pageSize = 90;
|
||||
let pageNumber = 0;
|
||||
const filteredWorkflowReminders: Array<Prisma.WorkflowReminderGetPayload<{ select: T }>> = [];
|
||||
|
||||
while (true) {
|
||||
const newFilteredWorkflowReminders = await prisma.workflowReminder.findMany({
|
||||
where: filter,
|
||||
select: select,
|
||||
skip: pageNumber * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
|
||||
if (newFilteredWorkflowReminders.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
filteredWorkflowReminders.push(
|
||||
...(newFilteredWorkflowReminders as Array<Prisma.WorkflowReminderGetPayload<{ select: T }>>)
|
||||
);
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
return filteredWorkflowReminders;
|
||||
}
|
||||
|
||||
type RemindersToDeleteType = { referenceId: string | null };
|
||||
|
||||
export async function getAllRemindersToDelete(): Promise<RemindersToDeleteType[]> {
|
||||
const whereFilter: Prisma.WorkflowReminderWhereInput = {
|
||||
method: WorkflowMethods.EMAIL,
|
||||
cancelled: true,
|
||||
scheduledDate: {
|
||||
lte: dayjs().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
const select: Prisma.WorkflowReminderSelect = {
|
||||
referenceId: true,
|
||||
};
|
||||
|
||||
const remindersToDelete = await getWorkflowReminders(whereFilter, select);
|
||||
|
||||
return remindersToDelete;
|
||||
}
|
||||
|
||||
type RemindersToCancelType = { referenceId: string | null; id: number };
|
||||
|
||||
export async function getAllRemindersToCancel(): Promise<RemindersToCancelType[]> {
|
||||
const whereFilter: Prisma.WorkflowReminderWhereInput = {
|
||||
cancelled: true,
|
||||
scheduled: true, //if it is false then they are already cancelled
|
||||
scheduledDate: {
|
||||
lte: dayjs().add(1, "hour").toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
const select: Prisma.WorkflowReminderSelect = {
|
||||
referenceId: true,
|
||||
id: true,
|
||||
};
|
||||
|
||||
const remindersToCancel = await getWorkflowReminders(whereFilter, select);
|
||||
|
||||
return remindersToCancel;
|
||||
}
|
||||
|
||||
export async function getAllUnscheduledReminders(): Promise<PartialWorkflowReminder[]> {
|
||||
const whereFilter: Prisma.WorkflowReminderWhereInput = {
|
||||
method: WorkflowMethods.EMAIL,
|
||||
scheduled: false,
|
||||
scheduledDate: {
|
||||
lte: dayjs().add(72, "hour").toISOString(),
|
||||
},
|
||||
OR: [{ cancelled: false }, { cancelled: null }],
|
||||
};
|
||||
|
||||
const select: Prisma.WorkflowReminderSelect = {
|
||||
id: true,
|
||||
scheduledDate: true,
|
||||
workflowStep: {
|
||||
select: {
|
||||
action: true,
|
||||
sendTo: true,
|
||||
reminderBody: true,
|
||||
emailSubject: true,
|
||||
template: true,
|
||||
sender: true,
|
||||
includeCalendarEvent: true,
|
||||
},
|
||||
},
|
||||
booking: {
|
||||
select: {
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
description: true,
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
timeZone: true,
|
||||
locale: true,
|
||||
username: true,
|
||||
timeFormat: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
},
|
||||
metadata: true,
|
||||
uid: true,
|
||||
customInputs: true,
|
||||
responses: true,
|
||||
attendees: true,
|
||||
eventType: {
|
||||
select: {
|
||||
bookingFields: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
recurringEvent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const unscheduledReminders = (await getWorkflowReminders(whereFilter, select)) as PartialWorkflowReminder[];
|
||||
|
||||
return unscheduledReminders;
|
||||
}
|
Loading…
Reference in New Issue
Block a user