perf: Fix N+1 queries and optimize database operations (#25630)

* perf: batch database operations and fix N+1 queries

* delete

* Fix condition for checking CalVideo location activity
This commit is contained in:
Anik Dhabal Babu
2025-12-09 07:32:30 +00:00
committed by GitHub
parent 978c5bdb95
commit 161ebdbbec
4 changed files with 120 additions and 75 deletions
@@ -277,39 +277,47 @@ const handleDeleteCredential = async ({
},
});
const unpaidBookingsIds = unpaidBookings.map((booking) => booking.id);
const unpaidBookingsPaymentIds = unpaidBookings.flatMap((booking) =>
booking.payment.map((payment) => payment.id)
);
await prisma.booking.updateMany({
where: {
id: {
in: unpaidBookingsIds,
},
},
data: {
status: BookingStatus.CANCELLED,
cancellationReason: "Payment method removed",
},
});
for (const paymentId of unpaidBookingsPaymentIds) {
await deletePayment(paymentId, credential);
}
await prisma.payment.deleteMany({
where: {
id: {
in: unpaidBookingsPaymentIds,
},
},
});
await prisma.attendee.deleteMany({
where: {
bookingId: {
in: unpaidBookingsIds,
},
},
});
await prisma.bookingReference.updateMany({
where: {
bookingId: {
in: unpaidBookingsIds,
},
},
data: { deleted: true },
});
for (const booking of unpaidBookings) {
await prisma.booking.update({
where: {
id: booking.id,
},
data: {
status: BookingStatus.CANCELLED,
cancellationReason: "Payment method removed",
},
});
for (const payment of booking.payment) {
await deletePayment(payment.id, credential);
await prisma.payment.delete({
where: {
id: payment.id,
},
});
}
await prisma.attendee.deleteMany({
where: {
bookingId: booking.id,
},
});
await prisma.bookingReference.updateMany({
where: {
bookingId: booking.id,
},
data: { deleted: true },
});
const attendeesListPromises = booking.attendees.map(async (attendee) => {
return {
name: attendee.name,
@@ -131,6 +131,7 @@ describe("createCRMEvent", () => {
// Set up Prisma mocks with proper return values
prismaMock.booking.findUnique.mockResolvedValueOnce(mockBooking);
prismaMock.credential.findUnique.mockResolvedValueOnce(mockCredential);
prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]);
prismaMock.bookingReference.createMany.mockResolvedValueOnce({ count: 1 });
prismaMock.bookingReference.findMany.mockResolvedValueOnce([]);
const payload = JSON.stringify({
@@ -198,6 +199,7 @@ describe("createCRMEvent", () => {
};
prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findMany.mockResolvedValueOnce([]);
const payload = JSON.stringify({
bookingUid: "booking-123",
@@ -238,6 +240,7 @@ describe("createCRMEvent", () => {
prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findUnique.mockResolvedValue(null);
prismaMock.credential.findMany.mockResolvedValueOnce([]);
prismaMock.bookingReference.findMany.mockResolvedValueOnce([]);
mockCreateEvent.mockRejectedValue(new Error("Salesforce API error"));
@@ -288,6 +291,7 @@ describe("createCRMEvent", () => {
prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findUnique.mockResolvedValue(mockCredential);
prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]);
prismaMock.bookingReference.findMany.mockResolvedValueOnce([]);
mockCreateEvent.mockRejectedValue(new RetryableError("Salesforce API Retryable error"));
@@ -334,6 +338,7 @@ describe("createCRMEvent", () => {
prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findUnique.mockResolvedValue(mockCredential);
prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]);
prismaMock.bookingReference.findMany.mockResolvedValueOnce([]);
mockCreateEvent.mockRejectedValue(new Error("Salesforce API error"));
@@ -401,6 +406,11 @@ describe("createCRMEvent", () => {
.mockResolvedValueOnce(mockSalesforceCredential)
.mockResolvedValueOnce(mockHubspotCredential);
prismaMock.credential.findMany.mockResolvedValueOnce([
mockSalesforceCredential,
mockHubspotCredential,
]);
prismaMock.bookingReference.findMany.mockResolvedValueOnce([]);
// Throw error for first app and resolve for second app
@@ -462,6 +472,7 @@ describe("createCRMEvent", () => {
prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findUnique.mockResolvedValueOnce(mockSalesforceCredential);
prismaMock.credential.findMany.mockResolvedValueOnce([mockSalesforceCredential]);
prismaMock.bookingReference.findMany.mockResolvedValueOnce([
{ id: 1, type: "salesforce_crm", uid: "sf-event-123", credentialId: 1, bookingId: 1 },
]);
@@ -111,52 +111,72 @@ export async function createCRMEvent(payload: string): Promise<void> {
});
const errorPerApp: Record<AppSlug, UnknownError> = {};
// Find enabled CRM apps for the event type
// Parse apps and collect credential IDs for enabled CRM apps
const appInfoMap = new Map<string, { app: any; credentialId: number }>();
const credentialIds = new Set<number>();
for (const appSlug of Object.keys(eventTypeAppMetadata)) {
const appData = eventTypeAppMetadata[appSlug as keyof typeof eventTypeAppMetadata];
const appDataSchema = appDataSchemas[appSlug as keyof typeof appDataSchemas];
if (!appData || !appDataSchema) {
throw new Error(`Could not find appData or appDataSchema for ${appSlug}`);
}
const appParse = appDataSchema.safeParse(appData);
if (!appParse.success) {
log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error);
continue;
}
const app = appParse.data;
const hasCrmCategory =
app.appCategories && app.appCategories.some((category: string) => category === "crm");
if (!app.enabled || !app.credentialId || !hasCrmCategory) {
log.info(`Skipping CRM app ${appSlug}`, {
enabled: app.enabled,
credentialId: app.credentialId,
hasCrmCategory,
});
continue;
}
appInfoMap.set(appSlug, { app, credentialId: app.credentialId });
credentialIds.add(app.credentialId);
}
const crmCredentials = await prisma.credential.findMany({
where: {
id: {
in: Array.from(credentialIds),
},
},
include: {
user: {
select: {
email: true,
},
},
},
});
const crmCredentialMap = new Map<number, (typeof crmCredentials)[number]>();
for (const credential of crmCredentials) {
crmCredentialMap.set(credential.id, credential);
}
//Find enabled CRM apps for the event type
for (const appSlug of Array.from(appInfoMap.keys())) {
const { app, credentialId } = appInfoMap.get(appSlug)!;
// Try Catch per app to ensure all apps are tried even if any of them throws an error
// If we want to retry for an error from this try catch, then that error must be thrown as a RetryableError
try {
const appData = eventTypeAppMetadata[appSlug as keyof typeof eventTypeAppMetadata];
const appDataSchema = appDataSchemas[appSlug as keyof typeof appDataSchemas];
if (!appData || !appDataSchema) {
throw new Error(`Could not find appData or appDataSchema for ${appSlug}`);
}
const appParse = appDataSchema.safeParse(appData);
if (!appParse.success) {
log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error);
continue;
}
const app = appParse.data;
const hasCrmCategory =
app.appCategories && app.appCategories.some((category: string) => category === "crm");
if (!app.enabled || !app.credentialId || !hasCrmCategory) {
log.info(`Skipping CRM app ${appSlug}`, {
enabled: app.enabled,
credentialId: app.credentialId,
hasCrmCategory,
});
continue;
}
const crmCredential = await prisma.credential.findUnique({
where: {
id: app.credentialId,
},
include: {
user: {
select: {
email: true,
},
},
},
});
const crmCredential = crmCredentialMap.get(credentialId);
if (!crmCredential) {
throw new Error(`Credential not found for credentialId: ${app.credentialId}`);
throw new Error(`Credential not found for credentialId: ${credentialId}`);
}
const existingBookingReferenceForTheCredential = existingBookingReferences.find(
@@ -71,13 +71,19 @@ async function getTeamMembers({
});
const userRepo = new UserRepository(prisma);
const users = memberships.map((membership) => membership.user);
const enrichedUsers = await userRepo.enrichUsersWithTheirProfileExcludingOrgMetadata(users);
const enrichedUserMap = new Map<number, (typeof enrichedUsers)[0]>();
enrichedUsers.forEach((enrichedUser) => {
enrichedUserMap.set(enrichedUser.id, enrichedUser);
});
const membershipWithUserProfile = [];
for (const membership of memberships) {
const enrichedUser = enrichedUserMap.get(membership.user.id);
if (!enrichedUser) continue;
membershipWithUserProfile.push({
...membership,
user: await userRepo.enrichUserWithItsProfileExcludingOrgMetadata({
user: membership.user,
}),
user: enrichedUser,
});
}