* refactor: Move @calcom/core to @calcom/lib * Merging imports * Clean up * Ignoreing type error for now
208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
import type { Prisma } from "@prisma/client";
|
|
|
|
import { appDataSchemas } from "@calcom/app-store/apps.schemas.generated";
|
|
import { RetryableError } from "@calcom/lib/crmManager/errors";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import prisma from "@calcom/prisma";
|
|
import { BookingStatus } from "@calcom/prisma/enums";
|
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
|
|
import buildCalendarEvent from "./lib/buildCalendarEvent";
|
|
import { createCRMEventSchema } from "./schema";
|
|
|
|
const log = logger.getSubLogger({ prefix: [`[[tasker] createCRMEvent`] });
|
|
type AppSlug = string;
|
|
type UnknownError = unknown;
|
|
|
|
const handleErrors = ({
|
|
errorPerApp,
|
|
payload,
|
|
}: {
|
|
errorPerApp: Record<AppSlug, UnknownError>;
|
|
payload: string;
|
|
}) => {
|
|
const errorMsgs = Object.entries(errorPerApp).map(([appSlug, error]) => {
|
|
if (error instanceof Error) {
|
|
return `(app: ${appSlug}) ${error.message}`;
|
|
}
|
|
return `(app: ${appSlug}) ${error}`;
|
|
});
|
|
|
|
if (errorMsgs.length > 0) {
|
|
const hasAnyRetryableErrors = Object.values(errorPerApp).some((error) => error instanceof RetryableError);
|
|
if (hasAnyRetryableErrors) {
|
|
// Intentional rethrow to trigger retry
|
|
throw new Error(errorMsgs.join("\n"));
|
|
}
|
|
log.error(`[Will not retry] Error creating CRM event for payload ${payload}: ${errorMsgs.join("\n")}`);
|
|
}
|
|
};
|
|
|
|
export async function createCRMEvent(payload: string): Promise<void> {
|
|
// All errors thrown from this try catch will be cause a retry
|
|
try {
|
|
const parsedPayload = createCRMEventSchema.safeParse(JSON.parse(payload));
|
|
|
|
if (!parsedPayload.success) {
|
|
throw new Error(`malformed payload in createCRMEvent: ${parsedPayload.error}`);
|
|
}
|
|
const { bookingUid } = parsedPayload.data;
|
|
|
|
const booking = await prisma.booking.findUnique({
|
|
where: {
|
|
uid: bookingUid,
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
locale: true,
|
|
username: true,
|
|
timeZone: true,
|
|
},
|
|
},
|
|
eventType: {
|
|
select: {
|
|
metadata: true,
|
|
},
|
|
},
|
|
references: {
|
|
select: {
|
|
type: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!booking) {
|
|
throw new Error(`booking not found for uid: ${bookingUid}`);
|
|
}
|
|
|
|
if (booking.status !== BookingStatus.ACCEPTED) {
|
|
log.info(`Booking status is not ACCEPTED`);
|
|
return;
|
|
}
|
|
|
|
if (!booking.user) {
|
|
throw new Error(`user not found for uid: ${bookingUid}`);
|
|
}
|
|
|
|
const eventTypeMetadata = EventTypeMetaDataSchema.safeParse(booking.eventType?.metadata);
|
|
|
|
if (!eventTypeMetadata.success) {
|
|
throw new Error(`malformed event type metadata: ${eventTypeMetadata.error}`);
|
|
}
|
|
|
|
const eventTypeAppMetadata = eventTypeMetadata.data?.apps;
|
|
|
|
if (!eventTypeAppMetadata) {
|
|
throw new Error(`event type app metadata not found for booking ${bookingUid}`);
|
|
}
|
|
|
|
const calendarEvent = await buildCalendarEvent(bookingUid);
|
|
|
|
const bookingReferencesToCreate: Prisma.BookingReferenceUncheckedCreateInput[] = [];
|
|
const existingBookingReferences = await prisma.bookingReference.findMany({
|
|
where: {
|
|
bookingId: booking.id,
|
|
},
|
|
});
|
|
|
|
const errorPerApp: Record<AppSlug, UnknownError> = {};
|
|
// Find enabled CRM apps for the event type
|
|
for (const appSlug of Object.keys(eventTypeAppMetadata)) {
|
|
// 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,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!crmCredential) {
|
|
throw new Error(`Credential not found for credentialId: ${app.credentialId}`);
|
|
}
|
|
|
|
const existingBookingReferenceForTheCredential = existingBookingReferences.find(
|
|
(reference) => reference.credentialId === crmCredential.id
|
|
);
|
|
|
|
if (existingBookingReferenceForTheCredential) {
|
|
log.info(`Skipping CRM app ${appSlug} as booking reference already exists`, {
|
|
credentialId: crmCredential.id,
|
|
bookingReferenceId: existingBookingReferenceForTheCredential.id,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
const CrmManager = (await import("@calcom/lib/crmManager/crmManager")).default;
|
|
|
|
const crm = new CrmManager(crmCredential, app);
|
|
|
|
const results = await crm.createEvent(calendarEvent);
|
|
|
|
if (results) {
|
|
bookingReferencesToCreate.push({
|
|
type: crmCredential.type,
|
|
uid: results.id,
|
|
meetingId: results.id,
|
|
credentialId: crmCredential.id,
|
|
bookingId: booking.id,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
errorPerApp[appSlug] = error;
|
|
}
|
|
}
|
|
|
|
await prisma.bookingReference.createMany({
|
|
data: bookingReferencesToCreate,
|
|
});
|
|
|
|
handleErrors({ errorPerApp, payload });
|
|
} catch (error) {
|
|
const errorMsg = `Error creating crm event: error: ${safeStringify(error)} Data: ${safeStringify({
|
|
payload,
|
|
})}`;
|
|
log.error(`[Will retry] ${errorMsg}`);
|
|
// Intentional rethrow to trigger retry
|
|
throw error;
|
|
}
|
|
}
|