refactor: Fix N+1 queries and optimize database operations (#25813)
* refactor: prisma N+1 Queries * addressed review
This commit is contained in:
@@ -47,7 +47,9 @@ export async function handler(req: NextRequest) {
|
||||
if (isSendgridEnabled) {
|
||||
const remindersToDelete: { referenceId: string | null; id: number }[] = await getAllRemindersToDelete();
|
||||
|
||||
const reminderIds: number[] = [];
|
||||
const handlePastCancelledReminders = remindersToDelete.map(async (reminder) => {
|
||||
reminderIds.push(reminder.id);
|
||||
try {
|
||||
if (reminder.referenceId) {
|
||||
await deleteScheduledSend(reminder.referenceId);
|
||||
@@ -55,19 +57,25 @@ export async function handler(req: NextRequest) {
|
||||
} catch (err) {
|
||||
logger.error(`Error deleting scheduled send (ref: ${reminder.referenceId}): ${err}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.workflowReminder.update({
|
||||
where: { id: reminder.id },
|
||||
data: { referenceId: null },
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Error updating reminder (id: ${reminder.id}): ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(handlePastCancelledReminders);
|
||||
|
||||
if (reminderIds.length > 0) {
|
||||
try {
|
||||
await prisma.workflowReminder.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: reminderIds,
|
||||
},
|
||||
},
|
||||
data: { referenceId: null },
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Error updating reminders: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
//cancel reminders for cancelled/rescheduled bookings that are scheduled within the next hour
|
||||
const remindersToCancel: { referenceId: string | null; id: number }[] = await getAllRemindersToCancel();
|
||||
|
||||
@@ -89,12 +97,11 @@ export async function handler(req: NextRequest) {
|
||||
cancelUpdatePromises.push(cancelPromise, updatePromise);
|
||||
}
|
||||
|
||||
Promise.allSettled(cancelUpdatePromises).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
logger.error(`Error cancelling scheduled_sends: ${result.reason}`);
|
||||
}
|
||||
});
|
||||
const results = await Promise.allSettled(cancelUpdatePromises);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
logger.error(`Error cancelling scheduled_sends: ${result.reason}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -490,12 +497,11 @@ export async function handler(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
Promise.allSettled(sendEmailPromises).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
logger.error("Email sending failed", result.reason);
|
||||
}
|
||||
});
|
||||
const sendResults = await Promise.allSettled(sendEmailPromises);
|
||||
sendResults.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
logger.error("Email sending failed", result.reason);
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({ message: `${unscheduledReminders.length} Emails to schedule` }, { status: 200 });
|
||||
|
||||
@@ -49,6 +49,31 @@ export class WorkflowReminderRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findWorkflowRemindersByStepIds(workflowStepIds: number[]) {
|
||||
if (workflowStepIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await prisma.workflowReminder.findMany({
|
||||
where: {
|
||||
workflowStepId: {
|
||||
in: workflowStepIds,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
referenceId: true,
|
||||
method: true,
|
||||
workflowStepId: true,
|
||||
booking: {
|
||||
select: {
|
||||
eventTypeId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async findWorkflowReminderForAIPhoneCallExecution(id: number) {
|
||||
const bookingSelect = {
|
||||
uid: true,
|
||||
|
||||
@@ -118,31 +118,27 @@ export const getEventTypesByViewer = async (user: User, filters?: Filters, forRo
|
||||
|
||||
const mapEventType = async (eventType: UserEventTypes) => {
|
||||
const userRepo = new UserRepository(prisma);
|
||||
const eventTypeUsers = eventType?.hosts?.length
|
||||
? eventType.hosts.map((host) => host.user)
|
||||
: eventType.users;
|
||||
const enrichedUsers = await userRepo.enrichUsersWithTheirProfiles(eventTypeUsers);
|
||||
|
||||
const children = eventType.children || [];
|
||||
const allChildUsers = children.flatMap((c) => c.users);
|
||||
const enrichedAllChildUsers = await userRepo.enrichUsersWithTheirProfiles(allChildUsers);
|
||||
const enrichedUsersMap = new Map(enrichedAllChildUsers.map((user) => [user.id, user]));
|
||||
|
||||
const enrichedChildren = children.map((c) => ({
|
||||
...c,
|
||||
users: c.users.map((user) => enrichedUsersMap.get(user.id)).filter((user) => !!user),
|
||||
}));
|
||||
|
||||
return {
|
||||
...eventType,
|
||||
safeDescription: eventType?.description ? markdownToSafeHTML(eventType.description) : undefined,
|
||||
users: await Promise.all(
|
||||
(eventType?.hosts?.length ? eventType?.hosts.map((host) => host.user) : eventType.users).map(
|
||||
async (u) =>
|
||||
await userRepo.enrichUserWithItsProfile({
|
||||
user: u,
|
||||
})
|
||||
)
|
||||
),
|
||||
users: enrichedUsers,
|
||||
metadata: eventType.metadata ? eventTypeMetaDataSchemaWithUntypedApps.parse(eventType.metadata) : null,
|
||||
children: await Promise.all(
|
||||
(eventType.children || []).map(async (c) => ({
|
||||
...c,
|
||||
users: await Promise.all(
|
||||
c.users.map(
|
||||
async (u) =>
|
||||
await userRepo.enrichUserWithItsProfile({
|
||||
user: u,
|
||||
})
|
||||
)
|
||||
),
|
||||
}))
|
||||
),
|
||||
children: enrichedChildren,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -430,34 +430,44 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
hostGroups.map(async (group) => {
|
||||
await ctx.prisma.hostGroup.upsert({
|
||||
where: { id: group.id },
|
||||
update: { name: group.name },
|
||||
create: {
|
||||
const existingGroupsMap = new Map(existingHostGroups.map((group) => [group.id, group]));
|
||||
const newGroupsMap = new Map(hostGroups.map((group) => [group.id, group]));
|
||||
|
||||
const groupsToCreate = hostGroups.filter((group) => !existingGroupsMap.has(group.id));
|
||||
const groupsToUpdate = hostGroups.filter((group) => existingGroupsMap.has(group.id));
|
||||
const groupsToDelete = existingHostGroups.filter((existingGroup) => !newGroupsMap.has(existingGroup.id));
|
||||
|
||||
await ctx.prisma.$transaction(async (tx) => {
|
||||
// Create new groups
|
||||
if (groupsToCreate.length > 0) {
|
||||
await tx.hostGroup.createMany({
|
||||
data: groupsToCreate.map((group) => ({
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
eventTypeId: id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
// Update existing groups
|
||||
for (const group of groupsToUpdate) {
|
||||
await tx.hostGroup.update({
|
||||
where: { id: group.id },
|
||||
data: { name: group.name },
|
||||
});
|
||||
}
|
||||
|
||||
// Delete groups that are no longer in the new list
|
||||
if (groupsToDelete.length > 0) {
|
||||
await tx.hostGroup.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: groupsToDelete.map((group) => group.id),
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const newGroupsMap = new Map(hostGroups.map((group) => [group.id, group]));
|
||||
|
||||
// Delete groups that are no longer in the new list
|
||||
const groupsToDelete = existingHostGroups.filter((existingGroup) => !newGroupsMap.has(existingGroup.id));
|
||||
|
||||
if (groupsToDelete.length > 0) {
|
||||
await ctx.prisma.hostGroup.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: groupsToDelete.map((group) => group.id),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (teamId && hosts) {
|
||||
|
||||
@@ -253,6 +253,22 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
|
||||
const agentRepo = new PrismaAgentRepository(prisma);
|
||||
|
||||
const stepIds = userWorkflow.steps.map((step) => step.id);
|
||||
const allReminders = await WorkflowReminderRepository.findWorkflowRemindersByStepIds(stepIds);
|
||||
|
||||
const remindersByStepId = new Map<number, (typeof allReminders)[number][]>();
|
||||
for (const reminder of allReminders) {
|
||||
const stepId = reminder.workflowStepId;
|
||||
if (stepId === null) continue;
|
||||
|
||||
let list = remindersByStepId.get(stepId);
|
||||
if (!list) {
|
||||
list = [];
|
||||
remindersByStepId.set(stepId, list);
|
||||
}
|
||||
list.push(reminder);
|
||||
}
|
||||
|
||||
// handle deleted and edited workflow steps
|
||||
await Promise.all(
|
||||
userWorkflow.steps.map(async (oldStep) => {
|
||||
@@ -271,7 +287,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
};
|
||||
}
|
||||
|
||||
const remindersFromStep = await WorkflowReminderRepository.findWorkflowRemindersByStepId(oldStep.id);
|
||||
const remindersFromStep = remindersByStepId.get(oldStep.id) || [];
|
||||
//step was deleted
|
||||
if (!newStep) {
|
||||
if (oldStep.action === WorkflowActions.CAL_AI_PHONE_CALL && !!oldStep.agentId) {
|
||||
|
||||
Reference in New Issue
Block a user