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:
@@ -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(
|
||||
|
||||
+9
-3
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user