refactor: Uses withReporting to force named functions for tracing (#21219)
This commit is contained in:
@@ -5,7 +5,7 @@ import { emailSchema } from "@calcom/lib/emailSchema";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import monitorCallbackAsync from "@calcom/lib/sentryWrapper";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { App_RoutingForms_Form } from "@calcom/prisma/client";
|
||||
import { RoutingFormSettings } from "@calcom/prisma/zod-utils";
|
||||
@@ -32,10 +32,6 @@ export type Form = SerializableForm<
|
||||
|
||||
const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/lib/handleResponse"] });
|
||||
|
||||
export const handleResponse = (...args: Parameters<typeof _handleResponse>) => {
|
||||
return monitorCallbackAsync(_handleResponse, ...args);
|
||||
};
|
||||
|
||||
const _handleResponse = async ({
|
||||
response,
|
||||
form,
|
||||
@@ -185,7 +181,7 @@ const _handleResponse = async ({
|
||||
})(),
|
||||
]);
|
||||
|
||||
await monitorCallbackAsync(getRoutedMembers);
|
||||
await withReporting(getRoutedMembers, "getRoutedMembers")();
|
||||
} else {
|
||||
// It currently happens for a Router route. Such a route id isn't present in the form.routes
|
||||
}
|
||||
@@ -202,8 +198,7 @@ const _handleResponse = async ({
|
||||
},
|
||||
});
|
||||
|
||||
await monitorCallbackAsync(
|
||||
onFormSubmission,
|
||||
await onFormSubmission(
|
||||
{ ...serializableFormWithFields, userWithEmails },
|
||||
dbFormResponse.response as FormResponse,
|
||||
dbFormResponse.id,
|
||||
@@ -247,3 +242,5 @@ const _handleResponse = async ({
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const handleResponse = withReporting(_handleResponse, "handleResponse");
|
||||
|
||||
@@ -6,6 +6,7 @@ import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/client";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
|
||||
@@ -84,7 +85,7 @@ export function getFieldResponse({
|
||||
* Not called in preview mode or dry run mode
|
||||
* It takes care of sending webhooks and emails for form submissions
|
||||
*/
|
||||
export async function onFormSubmission(
|
||||
async function _onFormSubmission(
|
||||
form: Ensure<
|
||||
SerializableForm<App_RoutingForms_Form> & { user: Pick<User, "id" | "email">; userWithEmails?: string[] },
|
||||
"fields"
|
||||
@@ -206,6 +207,7 @@ export async function onFormSubmission(
|
||||
}
|
||||
}
|
||||
}
|
||||
export const onFormSubmission = withReporting(_onFormSubmission, "onFormSubmission");
|
||||
|
||||
export const sendResponseEmail = async (
|
||||
form: Pick<App_RoutingForms_Form, "id" | "name" | "fields">,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getEventName } from "@calcom/lib/event";
|
||||
import { formatCalEvent } from "@calcom/lib/formatCalendarEvent";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
@@ -98,7 +99,7 @@ const eventTypeDisableHostEmail = (metadata?: EventTypeMetadata) => {
|
||||
return !!metadata?.disableStandardEmails?.all?.host;
|
||||
};
|
||||
|
||||
export const sendScheduledEmailsAndSMS = async (
|
||||
const _sendScheduledEmailsAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
eventNameObject?: EventNameObjectType,
|
||||
hostEmailDisabled?: boolean,
|
||||
@@ -145,6 +146,11 @@ export const sendScheduledEmailsAndSMS = async (
|
||||
await successfullyScheduledSms.sendSMSToAttendees();
|
||||
};
|
||||
|
||||
export const sendScheduledEmailsAndSMS = withReporting(
|
||||
_sendScheduledEmailsAndSMS,
|
||||
"sendScheduledEmailsAndSMS"
|
||||
);
|
||||
|
||||
// for rescheduled round robin booking that assigned new members
|
||||
export const sendRoundRobinScheduledEmailsAndSMS = async ({
|
||||
calEvent,
|
||||
@@ -245,7 +251,7 @@ export const sendRoundRobinCancelledEmailsAndSMS = async (
|
||||
await Promise.all(emailsAndSMSToSend);
|
||||
};
|
||||
|
||||
export const sendRescheduledEmailsAndSMS = async (
|
||||
const _sendRescheduledEmailsAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
eventTypeMetadata?: EventTypeMetadata
|
||||
) => {
|
||||
@@ -276,6 +282,10 @@ export const sendRescheduledEmailsAndSMS = async (
|
||||
const successfullyReScheduledSms = new EventSuccessfullyReScheduledSMS(calEvent);
|
||||
await successfullyReScheduledSms.sendSMSToAttendees();
|
||||
};
|
||||
export const sendRescheduledEmailsAndSMS = withReporting(
|
||||
_sendRescheduledEmailsAndSMS,
|
||||
"sendRescheduledEmailsAndSMS"
|
||||
);
|
||||
|
||||
export const sendRescheduledSeatEmailAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
@@ -370,10 +380,7 @@ export const sendCancelledSeatEmailsAndSMS = async (
|
||||
await cancelledSeatSMS.sendSMSToAttendee(cancelledAttendee);
|
||||
};
|
||||
|
||||
export const sendOrganizerRequestEmail = async (
|
||||
calEvent: CalendarEvent,
|
||||
eventTypeMetadata?: EventTypeMetadata
|
||||
) => {
|
||||
const _sendOrganizerRequestEmail = async (calEvent: CalendarEvent, eventTypeMetadata?: EventTypeMetadata) => {
|
||||
if (eventTypeDisableHostEmail(eventTypeMetadata)) return;
|
||||
const calendarEvent = formatCalEvent(calEvent);
|
||||
|
||||
@@ -390,7 +397,12 @@ export const sendOrganizerRequestEmail = async (
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
|
||||
export const sendAttendeeRequestEmailAndSMS = async (
|
||||
export const sendOrganizerRequestEmail = withReporting(
|
||||
_sendOrganizerRequestEmail,
|
||||
"sendOrganizerRequestEmail"
|
||||
);
|
||||
|
||||
const _sendAttendeeRequestEmailAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
attendee: Person,
|
||||
eventTypeMetadata?: EventTypeMetadata
|
||||
@@ -403,6 +415,11 @@ export const sendAttendeeRequestEmailAndSMS = async (
|
||||
await eventRequestSms.sendSMSToAttendee(attendee);
|
||||
};
|
||||
|
||||
export const sendAttendeeRequestEmailAndSMS = withReporting(
|
||||
_sendAttendeeRequestEmailAndSMS,
|
||||
"sendAttendeeRequestEmailAndSMS"
|
||||
);
|
||||
|
||||
export const sendDeclinedEmailsAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
eventTypeMetadata?: EventTypeMetadata
|
||||
|
||||
+7
-3
@@ -2,6 +2,7 @@ import async from "async";
|
||||
|
||||
import { isDelegationCredential } from "@calcom/lib/delegationCredential/clientAndServer";
|
||||
import { buildAllCredentials } from "@calcom/lib/delegationCredential/server";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
import { refreshCredential } from "./refreshCredential";
|
||||
@@ -11,9 +12,10 @@ import { refreshCredential } from "./refreshCredential";
|
||||
*
|
||||
* @param credentials
|
||||
*/
|
||||
export async function refreshCredentials(
|
||||
// Define the function with underscore prefix
|
||||
const _refreshCredentials = async (
|
||||
credentials: Array<CredentialForCalendarService>
|
||||
): Promise<Array<CredentialForCalendarService>> {
|
||||
): Promise<Array<CredentialForCalendarService>> => {
|
||||
const nonDelegationCredentials = credentials.filter(
|
||||
(cred) => !isDelegationCredential({ credentialId: cred.id })
|
||||
);
|
||||
@@ -22,4 +24,6 @@ export async function refreshCredentials(
|
||||
);
|
||||
const refreshedDbCredentials = await async.mapLimit(nonDelegationCredentials, 5, refreshCredential);
|
||||
return buildAllCredentials({ delegationCredentials, existingCredentials: refreshedDbCredentials });
|
||||
}
|
||||
};
|
||||
|
||||
export const refreshCredentials = withReporting(_refreshCredentials, "refreshCredentials");
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "@calcom/emails";
|
||||
import getICalUID from "@calcom/emails/lib/getICalUID";
|
||||
import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import { getShouldServeCache } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
@@ -46,7 +45,7 @@ import {
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import EventManager from "@calcom/lib/EventManager";
|
||||
import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils";
|
||||
import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import {
|
||||
enrichHostsWithDelegationCredentials,
|
||||
getFirstDelegationConferencingCredentialAppLocation,
|
||||
@@ -64,7 +63,6 @@ import logger from "@calcom/lib/logger";
|
||||
import { handlePayment } from "@calcom/lib/payment/handlePayment";
|
||||
import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import monitorCallbackAsync from "@calcom/lib/sentryWrapper";
|
||||
import { getLuckyUser } from "@calcom/lib/server/getLuckyUser";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
|
||||
@@ -94,7 +92,7 @@ import type { Booking } from "./handleNewBooking/createBooking";
|
||||
import { ensureAvailableUsers } from "./handleNewBooking/ensureAvailableUsers";
|
||||
import { getBookingData } from "./handleNewBooking/getBookingData";
|
||||
import { getCustomInputsResponses } from "./handleNewBooking/getCustomInputsResponses";
|
||||
import { getEventTypesFromDB } from "./handleNewBooking/getEventTypesFromDB";
|
||||
import { getEventType } from "./handleNewBooking/getEventType";
|
||||
import type { getEventTypeResponse } from "./handleNewBooking/getEventTypesFromDB";
|
||||
import { getLocationValuesForDb } from "./handleNewBooking/getLocationValuesForDb";
|
||||
import { getRequiresConfirmationFlags } from "./handleNewBooking/getRequiresConfirmationFlags";
|
||||
@@ -140,25 +138,6 @@ function getICalSequence(originalRescheduledBooking: BookingType | null) {
|
||||
return originalRescheduledBooking.iCalSequence + 1;
|
||||
}
|
||||
|
||||
const getEventType = async ({
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
eventTypeSlug?: string;
|
||||
}) => {
|
||||
// handle dynamic user
|
||||
const eventType =
|
||||
!eventTypeId && !!eventTypeSlug ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId);
|
||||
|
||||
const isOrgTeamEvent = !!eventType?.team && !!eventType?.team?.parentId;
|
||||
|
||||
return {
|
||||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields({ ...eventType, isOrgTeamEvent }),
|
||||
};
|
||||
};
|
||||
|
||||
type BookingDataSchemaGetter =
|
||||
| typeof getBookingDataSchema
|
||||
| typeof import("@calcom/features/bookings/lib/getBookingDataSchemaForApi").default;
|
||||
@@ -347,7 +326,7 @@ async function handler(
|
||||
|
||||
const isPlatformBooking = !!platformClientId;
|
||||
|
||||
const eventType = await monitorCallbackAsync(getEventType, {
|
||||
const eventType = await getEventType({
|
||||
eventTypeId: rawBookingData.eventTypeId,
|
||||
eventTypeSlug: rawBookingData.eventTypeSlug,
|
||||
});
|
||||
@@ -551,22 +530,19 @@ async function handler(
|
||||
});
|
||||
}
|
||||
|
||||
const { qualifiedRRUsers, additionalFallbackRRUsers, fixedUsers } = await monitorCallbackAsync(
|
||||
loadAndValidateUsers,
|
||||
{
|
||||
hostname,
|
||||
forcedSlug,
|
||||
isPlatform: isPlatformBooking,
|
||||
eventType,
|
||||
eventTypeId,
|
||||
dynamicUserList,
|
||||
logger: loggerWithEventDetails,
|
||||
routedTeamMemberIds: routedTeamMemberIds ?? null,
|
||||
contactOwnerEmail,
|
||||
rescheduleUid: reqBody.rescheduleUid || null,
|
||||
routingFormResponse,
|
||||
}
|
||||
);
|
||||
const { qualifiedRRUsers, additionalFallbackRRUsers, fixedUsers } = await loadAndValidateUsers({
|
||||
hostname,
|
||||
forcedSlug,
|
||||
isPlatform: isPlatformBooking,
|
||||
eventType,
|
||||
eventTypeId,
|
||||
dynamicUserList,
|
||||
logger: loggerWithEventDetails,
|
||||
routedTeamMemberIds: routedTeamMemberIds ?? null,
|
||||
contactOwnerEmail,
|
||||
rescheduleUid: reqBody.rescheduleUid || null,
|
||||
routingFormResponse,
|
||||
});
|
||||
|
||||
// We filter out users but ensure allHostUsers remain same.
|
||||
let users = [...qualifiedRRUsers, ...additionalFallbackRRUsers, ...fixedUsers];
|
||||
@@ -579,7 +555,7 @@ async function handler(
|
||||
location,
|
||||
});
|
||||
|
||||
await monitorCallbackAsync(checkBookingAndDurationLimits, {
|
||||
await checkBookingAndDurationLimits({
|
||||
eventType,
|
||||
reqBodyStart: reqBody.start,
|
||||
reqBodyRescheduleUid: reqBody.rescheduleUid,
|
||||
@@ -1289,7 +1265,7 @@ async function handler(
|
||||
|
||||
try {
|
||||
if (!isDryRun) {
|
||||
booking = await monitorCallbackAsync(createBooking, {
|
||||
booking = await createBooking({
|
||||
uid,
|
||||
rescheduledBy: reqBody.rescheduledBy,
|
||||
routingFormResponseId: routingFormResponseId,
|
||||
@@ -1327,7 +1303,7 @@ async function handler(
|
||||
// If it's a round robin event, record the reason for the host assignment
|
||||
if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
if (reqBody.crmOwnerRecordType && reqBody.crmAppSlug && contactOwnerEmail && routingFormResponseId) {
|
||||
await monitorCallbackAsync(AssignmentReasonRecorder.CRMOwnership, {
|
||||
await AssignmentReasonRecorder.CRMOwnership({
|
||||
bookingId: booking.id,
|
||||
crmAppSlug: reqBody.crmAppSlug,
|
||||
teamMemberEmail: contactOwnerEmail,
|
||||
@@ -1335,7 +1311,7 @@ async function handler(
|
||||
routingFormResponseId,
|
||||
});
|
||||
} else if (routingFormResponseId && teamId) {
|
||||
await monitorCallbackAsync(AssignmentReasonRecorder.routingFormRoute, {
|
||||
await AssignmentReasonRecorder.routingFormRoute({
|
||||
bookingId: booking.id,
|
||||
routingFormResponseId,
|
||||
organizerId: organizerUser.id,
|
||||
@@ -1417,7 +1393,7 @@ async function handler(
|
||||
}
|
||||
|
||||
// After polling videoBusyTimes, credentials might have been changed due to refreshment, so query them again.
|
||||
const credentials = await monitorCallbackAsync(refreshCredentials, allCredentials);
|
||||
const credentials = await refreshCredentials(allCredentials);
|
||||
const apps = eventTypeAppMetadataOptionalSchema.parse(eventType?.metadata?.apps);
|
||||
const eventManager = !isDryRun
|
||||
? new EventManager({ ...organizerUser, credentials }, apps)
|
||||
@@ -1798,8 +1774,7 @@ async function handler(
|
||||
);
|
||||
|
||||
if (!isDryRun) {
|
||||
await monitorCallbackAsync(
|
||||
sendScheduledEmailsAndSMS,
|
||||
await sendScheduledEmailsAndSMS(
|
||||
{
|
||||
...evt,
|
||||
additionalInformation,
|
||||
@@ -1840,13 +1815,8 @@ async function handler(
|
||||
})
|
||||
);
|
||||
if (!isDryRun) {
|
||||
await monitorCallbackAsync(sendOrganizerRequestEmail, { ...evt, additionalNotes }, eventType.metadata);
|
||||
await monitorCallbackAsync(
|
||||
sendAttendeeRequestEmailAndSMS,
|
||||
{ ...evt, additionalNotes },
|
||||
attendeesList[0],
|
||||
eventType.metadata
|
||||
);
|
||||
await sendOrganizerRequestEmail({ ...evt, additionalNotes }, eventType.metadata);
|
||||
await sendAttendeeRequestEmailAndSMS({ ...evt, additionalNotes }, attendeesList[0], eventType.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1969,11 +1939,8 @@ async function handler(
|
||||
|
||||
// We are here so, booking doesn't require payment and booking is also created in DB already, through createBooking call
|
||||
if (isConfirmedByDefault) {
|
||||
const subscribersMeetingEnded = await monitorCallbackAsync(getWebhooks, subscriberOptionsMeetingEnded);
|
||||
const subscribersMeetingStarted = await monitorCallbackAsync(
|
||||
getWebhooks,
|
||||
subscriberOptionsMeetingStarted
|
||||
);
|
||||
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||
const subscribersMeetingStarted = await getWebhooks(subscriberOptionsMeetingStarted);
|
||||
|
||||
let deleteWebhookScheduledTriggerPromise: Promise<unknown> = Promise.resolve();
|
||||
const scheduleTriggerPromises = [];
|
||||
@@ -2024,7 +1991,7 @@ async function handler(
|
||||
});
|
||||
|
||||
// Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED
|
||||
await monitorCallbackAsync(handleWebhookTrigger, {
|
||||
await handleWebhookTrigger({
|
||||
subscriberOptions,
|
||||
eventTrigger,
|
||||
webhookData,
|
||||
@@ -2035,7 +2002,7 @@ async function handler(
|
||||
const eventTrigger: WebhookTriggerEvents = WebhookTriggerEvents.BOOKING_REQUESTED;
|
||||
subscriberOptions.triggerEvent = eventTrigger;
|
||||
webhookData.status = "PENDING";
|
||||
await monitorCallbackAsync(handleWebhookTrigger, {
|
||||
await handleWebhookTrigger({
|
||||
subscriberOptions,
|
||||
eventTrigger,
|
||||
webhookData,
|
||||
@@ -2099,7 +2066,7 @@ async function handler(
|
||||
}
|
||||
|
||||
try {
|
||||
await monitorCallbackAsync(scheduleWorkflowReminders, {
|
||||
await scheduleWorkflowReminders({
|
||||
workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: evtWithMetadata,
|
||||
@@ -2118,7 +2085,7 @@ async function handler(
|
||||
|
||||
try {
|
||||
if (isConfirmedByDefault) {
|
||||
await monitorCallbackAsync(scheduleNoShowTriggers, {
|
||||
await scheduleNoShowTriggers({
|
||||
booking: { startTime: booking.startTime, id: booking.id, location: booking.location },
|
||||
triggerForUser,
|
||||
organizerUser: { id: organizerUser.id },
|
||||
|
||||
@@ -2,6 +2,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import { checkBookingLimits } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
|
||||
import { checkDurationLimits } from "@calcom/lib/intervalLimits/server/checkDurationLimits";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
import type { NewBookingEventType } from "./getEventTypesFromDB";
|
||||
|
||||
@@ -13,7 +14,7 @@ type InputProps = {
|
||||
reqBodyRescheduleUid?: string;
|
||||
};
|
||||
|
||||
export const checkBookingAndDurationLimits = async ({
|
||||
const _checkBookingAndDurationLimits = async ({
|
||||
eventType,
|
||||
reqBodyStart,
|
||||
reqBodyRescheduleUid,
|
||||
@@ -42,3 +43,8 @@ export const checkBookingAndDurationLimits = async ({
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const checkBookingAndDurationLimits = withReporting(
|
||||
_checkBookingAndDurationLimits,
|
||||
"checkBookingAndDurationLimits"
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { z } from "zod";
|
||||
import type { routingFormResponseInDbSchema } from "@calcom/app-store/routing-forms/zod";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import type { CreationSource } from "@calcom/prisma/enums";
|
||||
@@ -71,7 +72,8 @@ async function getAssociatedBookingForFormResponse(formResponseId: number) {
|
||||
return formResponse?.routedToBookingUid ?? null;
|
||||
}
|
||||
|
||||
export async function createBooking({
|
||||
// Define the function with underscore prefix
|
||||
const _createBooking = async ({
|
||||
uid,
|
||||
reqBody,
|
||||
eventType,
|
||||
@@ -83,7 +85,7 @@ export async function createBooking({
|
||||
rescheduledBy,
|
||||
creationSource,
|
||||
tracking,
|
||||
}: CreateBookingParams & { rescheduledBy: string | undefined }) {
|
||||
}: CreateBookingParams & { rescheduledBy: string | undefined }) => {
|
||||
updateEventDetails(evt, originalRescheduledBooking);
|
||||
const associatedBookingForFormResponse = routingFormResponseId
|
||||
? await getAssociatedBookingForFormResponse(routingFormResponseId)
|
||||
@@ -126,7 +128,9 @@ export async function createBooking({
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createBooking = withReporting(_createBooking, "createBooking");
|
||||
|
||||
async function saveBooking(
|
||||
bookingAndAssociatedData: ReturnType<typeof buildNewBookingData>,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits";
|
||||
import { parseDurationLimit } from "@calcom/lib/intervalLimits/isDurationLimits";
|
||||
import { getPiiFreeUser } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
import type { getEventTypeResponse } from "./getEventTypesFromDB";
|
||||
import type { BookingType } from "./originalRescheduledBookingUtils";
|
||||
@@ -50,7 +51,7 @@ const hasDateRangeForBooking = (
|
||||
return dateRangeForBooking;
|
||||
};
|
||||
|
||||
export async function ensureAvailableUsers(
|
||||
const _ensureAvailableUsers = async (
|
||||
eventType: Omit<getEventTypeResponse, "users"> & {
|
||||
users: IsFixedAwareUser[];
|
||||
},
|
||||
@@ -58,7 +59,7 @@ export async function ensureAvailableUsers(
|
||||
loggerWithEventDetails: Logger<unknown>,
|
||||
shouldServeCache?: boolean
|
||||
// ReturnType hint of at least one IsFixedAwareUser, as it's made sure at least one entry exists
|
||||
): Promise<[IsFixedAwareUser, ...IsFixedAwareUser[]]> {
|
||||
): Promise<[IsFixedAwareUser, ...IsFixedAwareUser[]]> => {
|
||||
const availableUsers: IsFixedAwareUser[] = [];
|
||||
|
||||
const startDateTimeUtc = getDateTimeInUtc(input.dateFrom, input.timeZone);
|
||||
@@ -162,4 +163,6 @@ export async function ensureAvailableUsers(
|
||||
}
|
||||
// make sure TypeScript understands availableUsers is at least one.
|
||||
return availableUsers.length === 1 ? [availableUsers[0]] : [availableUsers[0], ...availableUsers.slice(1)];
|
||||
}
|
||||
};
|
||||
|
||||
export const ensureAvailableUsers = withReporting(_ensureAvailableUsers, "ensureAvailableUsers");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const findBookingQuery = async (bookingId: number) => {
|
||||
// Define the function with underscore prefix
|
||||
const _findBookingQuery = async (bookingId: number) => {
|
||||
const foundBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: bookingId,
|
||||
@@ -46,3 +48,5 @@ export const findBookingQuery = async (bookingId: number) => {
|
||||
// Don't leak any sensitive data
|
||||
return foundBooking;
|
||||
};
|
||||
|
||||
export const findBookingQuery = withReporting(_findBookingQuery, "findBookingQuery");
|
||||
|
||||
@@ -4,6 +4,7 @@ import type z from "zod";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { bookingCreateSchemaLegacyPropsForApi } from "@calcom/prisma/zod/custom/booking";
|
||||
|
||||
import type { TgetBookingDataSchema } from "../getBookingDataSchema";
|
||||
@@ -12,7 +13,8 @@ import { handleCustomInputs } from "./handleCustomInputs";
|
||||
|
||||
type ReqBodyWithEnd = TgetBookingDataSchema & { end: string };
|
||||
|
||||
export async function getBookingData<T extends z.ZodType>({
|
||||
// Define the function with underscore prefix
|
||||
const _getBookingData = async <T extends z.ZodType>({
|
||||
reqBody,
|
||||
eventType,
|
||||
schema,
|
||||
@@ -20,7 +22,7 @@ export async function getBookingData<T extends z.ZodType>({
|
||||
reqBody: Record<string, any>;
|
||||
eventType: getEventTypeResponse;
|
||||
schema: T;
|
||||
}) {
|
||||
}) => {
|
||||
const parsedBody = await schema.parseAsync(reqBody);
|
||||
const parsedBodyWithEnd = (body: TgetBookingDataSchema): body is ReqBodyWithEnd => {
|
||||
// Use the event length to auto-set the event end time.
|
||||
@@ -78,9 +80,11 @@ export async function getBookingData<T extends z.ZodType>({
|
||||
// So TS doesn't complain about unknown properties
|
||||
customInputs: undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export type AwaitedBookingData = Awaited<ReturnType<typeof getBookingData>>;
|
||||
export const getBookingData = withReporting(_getBookingData, "getBookingData");
|
||||
|
||||
export type AwaitedBookingData = Awaited<ReturnType<typeof _getBookingData>>;
|
||||
export type RescheduleReason = AwaitedBookingData["rescheduleReason"];
|
||||
export type NoEmail = AwaitedBookingData["noEmail"];
|
||||
export type AdditionalNotes = AwaitedBookingData["notes"];
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
import { getBookingFieldsWithSystemFields } from "../getBookingFields";
|
||||
import { getEventTypesFromDB } from "./getEventTypesFromDB";
|
||||
|
||||
const _getEventType = async ({
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
eventTypeSlug?: string;
|
||||
}) => {
|
||||
// handle dynamic user
|
||||
const eventType =
|
||||
!eventTypeId && !!eventTypeSlug ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId);
|
||||
|
||||
const isOrgTeamEvent = !!eventType?.team && !!eventType?.team?.parentId;
|
||||
|
||||
return {
|
||||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields({ ...eventType, isOrgTeamEvent }),
|
||||
};
|
||||
};
|
||||
|
||||
export const getEventType = withReporting(_getEventType, "getEventType");
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getFirstDelegationConferencingCredentialAppLocation } from "@calcom/lib/delegationCredential/server";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
@@ -14,7 +15,7 @@ const sortUsersByDynamicList = <TUser extends { username: string | null }>(
|
||||
});
|
||||
};
|
||||
|
||||
export const getLocationValuesForDb = <
|
||||
const _getLocationValuesForDb = <
|
||||
TUser extends {
|
||||
username: string | null;
|
||||
metadata: Prisma.JsonValue;
|
||||
@@ -53,3 +54,5 @@ export const getLocationValuesForDb = <
|
||||
organizerOrFirstDynamicGroupMemberDefaultLocationUrl: firstDynamicGroupMemberDefaultLocationUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export const getLocationValuesForDb = withReporting(_getLocationValuesForDb, "getLocationValuesForDb");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { checkIfFreeEmailDomain } from "@calcom/lib/freeEmailDomainCheck/checkIfFreeEmailDomain";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
import type { getEventTypeResponse } from "./getEventTypesFromDB";
|
||||
|
||||
@@ -44,11 +45,12 @@ export async function getRequiresConfirmationFlags({
|
||||
};
|
||||
}
|
||||
|
||||
async function determineRequiresConfirmation(
|
||||
// Define the function with underscore prefix
|
||||
const _determineRequiresConfirmation = async (
|
||||
eventType: EventType,
|
||||
bookingStartTime: string,
|
||||
bookerEmail: string
|
||||
): Promise<boolean> {
|
||||
): Promise<boolean> => {
|
||||
let requiresConfirmation = eventType?.requiresConfirmation;
|
||||
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold;
|
||||
const requiresConfirmationForFreeEmail = eventType?.requiresConfirmationForFreeEmail;
|
||||
@@ -65,7 +67,12 @@ async function determineRequiresConfirmation(
|
||||
}
|
||||
|
||||
return requiresConfirmation;
|
||||
}
|
||||
};
|
||||
|
||||
export const determineRequiresConfirmation = withReporting(
|
||||
_determineRequiresConfirmation,
|
||||
"determineRequiresConfirmation"
|
||||
);
|
||||
|
||||
function isUserReschedulingOwner(
|
||||
userId: number | undefined,
|
||||
|
||||
@@ -8,6 +8,7 @@ import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { getPiiFreeUser } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { RoutingFormResponse } from "@calcom/lib/server/getLuckyUser";
|
||||
import { withSelectedCalendars } from "@calcom/lib/server/repository/user";
|
||||
import { userSelect } from "@calcom/prisma";
|
||||
@@ -67,7 +68,7 @@ type InputProps = {
|
||||
forcedSlug: string | undefined;
|
||||
};
|
||||
|
||||
export async function loadAndValidateUsers({
|
||||
const _loadAndValidateUsers = async ({
|
||||
eventType,
|
||||
eventTypeId,
|
||||
dynamicUserList,
|
||||
@@ -83,7 +84,7 @@ export async function loadAndValidateUsers({
|
||||
qualifiedRRUsers: UsersWithDelegationCredentials;
|
||||
additionalFallbackRRUsers: UsersWithDelegationCredentials;
|
||||
fixedUsers: UsersWithDelegationCredentials;
|
||||
}> {
|
||||
}> => {
|
||||
let users: Users = await loadUsers({
|
||||
eventType,
|
||||
dynamicUserList,
|
||||
@@ -222,4 +223,6 @@ export async function loadAndValidateUsers({
|
||||
additionalFallbackRRUsers, // without qualified
|
||||
fixedUsers,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const loadAndValidateUsers = withReporting(_loadAndValidateUsers, "loadAndValidateUsers");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import tasker from "@calcom/features/tasker";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
|
||||
type ScheduleNoShowTriggersArgs = {
|
||||
@@ -19,7 +20,7 @@ type ScheduleNoShowTriggersArgs = {
|
||||
isDryRun?: boolean;
|
||||
};
|
||||
|
||||
export const scheduleNoShowTriggers = async (args: ScheduleNoShowTriggersArgs) => {
|
||||
const _scheduleNoShowTriggers = async (args: ScheduleNoShowTriggersArgs) => {
|
||||
const {
|
||||
booking,
|
||||
triggerForUser,
|
||||
@@ -110,3 +111,5 @@ export const scheduleNoShowTriggers = async (args: ScheduleNoShowTriggersArgs) =
|
||||
// (workflow) => workflow.trigger === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
|
||||
// );
|
||||
};
|
||||
|
||||
export const scheduleNoShowTriggers = withReporting(_scheduleNoShowTriggers, "scheduleNoShowTriggers");
|
||||
|
||||
+8
-1
@@ -4,6 +4,7 @@ import { getUTCOffsetByTimezone } from "@calcom/lib/dayjs";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import isOutOfBounds, { BookingDateInPastError } from "@calcom/lib/isOutOfBounds";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { EventType } from "@calcom/prisma/client";
|
||||
|
||||
type ValidateBookingTimeEventType = Pick<
|
||||
@@ -19,7 +20,8 @@ type ValidateBookingTimeEventType = Pick<
|
||||
| "title"
|
||||
>;
|
||||
|
||||
export const validateBookingTimeIsNotOutOfBounds = async <T extends ValidateBookingTimeEventType>(
|
||||
// Define the function with underscore prefix
|
||||
const _validateBookingTimeIsNotOutOfBounds = async <T extends ValidateBookingTimeEventType>(
|
||||
reqBodyStartTime: string,
|
||||
reqBodyTimeZone: string,
|
||||
eventType: T,
|
||||
@@ -54,3 +56,8 @@ export const validateBookingTimeIsNotOutOfBounds = async <T extends ValidateBook
|
||||
|
||||
if (timeOutOfBounds) throw new HttpError({ statusCode: 400, message: ErrorCode.BookingTimeOutOfBounds });
|
||||
};
|
||||
|
||||
export const validateBookingTimeIsNotOutOfBounds = withReporting(
|
||||
_validateBookingTimeIsNotOutOfBounds,
|
||||
"validateBookingTimeIsNotOutOfBounds"
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Logger } from "tslog";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
type Props = {
|
||||
reqBodyStart: string;
|
||||
@@ -11,7 +12,8 @@ type Props = {
|
||||
logger: Logger<unknown>;
|
||||
};
|
||||
|
||||
export const validateEventLength = ({
|
||||
// Define the function with underscore prefix
|
||||
const _validateEventLength = ({
|
||||
reqBodyStart,
|
||||
reqBodyEnd,
|
||||
eventTypeMultipleDuration,
|
||||
@@ -25,3 +27,5 @@ export const validateEventLength = ({
|
||||
throw new HttpError({ statusCode: 400, message: "Invalid event length" });
|
||||
}
|
||||
};
|
||||
|
||||
export const validateEventLength = withReporting(_validateEventLength, "validateEventLength");
|
||||
|
||||
@@ -4,8 +4,9 @@ import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
import { isEventPayload, type WebhookPayloadType } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
export async function handleWebhookTrigger(args: {
|
||||
async function _handleWebhookTrigger(args: {
|
||||
subscriberOptions: GetSubscriberOptions;
|
||||
eventTrigger: string;
|
||||
webhookData: WebhookPayloadType;
|
||||
@@ -32,3 +33,5 @@ export async function handleWebhookTrigger(args: {
|
||||
logger.error("Error while sending webhook", error);
|
||||
}
|
||||
}
|
||||
|
||||
export const handleWebhookTrigger = withReporting(_handleWebhookTrigger, "handleWebhookTrigger");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { FormResponse, Fields } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { zodRoutes } from "@calcom/app-store/routing-forms/zod";
|
||||
import { acrossQueryValueCompatiblity } from "@calcom/lib/raqb/raqbUtils";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { getUsersAttributes } from "@calcom/lib/service/attribute/server/getAttributes";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { AssignmentReasonEnum } from "@calcom/prisma/enums";
|
||||
@@ -13,7 +14,16 @@ export enum RRReassignmentType {
|
||||
}
|
||||
|
||||
export default class AssignmentReasonRecorder {
|
||||
static async routingFormRoute({
|
||||
/**
|
||||
* We should use decorators to wrap the methods with withReporting
|
||||
* but we can't don't have support for static methods in decorators
|
||||
* so this is a workaround to wrap the methods with withReporting.
|
||||
*/
|
||||
static routingFormRoute = withReporting(
|
||||
AssignmentReasonRecorder._routingFormRoute,
|
||||
"AssignmentReasonRecorder.routingFormRoute"
|
||||
);
|
||||
static async _routingFormRoute({
|
||||
bookingId,
|
||||
routingFormResponseId,
|
||||
organizerId,
|
||||
@@ -113,8 +123,11 @@ export default class AssignmentReasonRecorder {
|
||||
}
|
||||
|
||||
// Separate method to handle rerouting
|
||||
|
||||
static async CRMOwnership({
|
||||
static CRMOwnership = withReporting(
|
||||
AssignmentReasonRecorder._CRMOwnership,
|
||||
"AssignmentReasonRecorder.CRMOwnership"
|
||||
);
|
||||
static async _CRMOwnership({
|
||||
bookingId,
|
||||
crmAppSlug,
|
||||
teamMemberEmail,
|
||||
@@ -143,8 +156,11 @@ export default class AssignmentReasonRecorder {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async roundRobinReassignment({
|
||||
static roundRobinReassignment = withReporting(
|
||||
AssignmentReasonRecorder._roundRobinReassignment,
|
||||
"AssignmentReasonRecorder.roundRobinReassignment"
|
||||
);
|
||||
static async _roundRobinReassignment({
|
||||
bookingId,
|
||||
reassignById,
|
||||
reassignReason,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types";
|
||||
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { SENDER_NAME } from "@calcom/lib/constants";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { SchedulingType, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
@@ -177,7 +178,7 @@ const processWorkflowStep = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) => {
|
||||
const _scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) => {
|
||||
const {
|
||||
workflows,
|
||||
smsReminderNumber,
|
||||
@@ -232,7 +233,7 @@ export interface SendCancelledRemindersArgs {
|
||||
hideBranding?: boolean;
|
||||
}
|
||||
|
||||
export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
|
||||
const _sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
|
||||
const { smsReminderNumber, evt, workflows, hideBranding } = args;
|
||||
|
||||
if (!workflows.length) return;
|
||||
@@ -241,7 +242,7 @@ export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) =
|
||||
if (workflow.trigger !== WorkflowTriggerEvents.EVENT_CANCELLED) continue;
|
||||
|
||||
for (const step of workflow.steps) {
|
||||
processWorkflowStep(workflow, step, {
|
||||
await processWorkflowStep(workflow, step, {
|
||||
smsReminderNumber,
|
||||
hideBranding,
|
||||
calendarEvent: evt,
|
||||
@@ -249,3 +250,10 @@ export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) =
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Export functions wrapped with withReporting
|
||||
export const scheduleWorkflowReminders = withReporting(
|
||||
_scheduleWorkflowReminders,
|
||||
"scheduleWorkflowReminders"
|
||||
);
|
||||
export const sendCancelledReminders = withReporting(_sendCancelledReminders, "sendCancelledReminders");
|
||||
|
||||
@@ -3,6 +3,7 @@ import { scheduleEmailReminder } from "@calcom/features/ee/workflows/lib/reminde
|
||||
import type { Workflow } from "@calcom/features/ee/workflows/lib/types";
|
||||
import type { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { WorkflowTriggerEvents, TimeUnit, WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
|
||||
import type { ExtendedCalendarEvent } from "./reminderScheduler";
|
||||
@@ -11,7 +12,7 @@ const log = logger.getSubLogger({ prefix: ["[scheduleMandatoryReminder]"] });
|
||||
|
||||
export type NewBookingEventType = Awaited<ReturnType<typeof getDefaultEvent>> | getEventTypeResponse;
|
||||
|
||||
export async function scheduleMandatoryReminder({
|
||||
async function _scheduleMandatoryReminder({
|
||||
evt,
|
||||
workflows,
|
||||
requiresConfirmation,
|
||||
@@ -75,3 +76,8 @@ export async function scheduleMandatoryReminder({
|
||||
log.error("Error while scheduling mandatory reminders", JSON.stringify({ error }));
|
||||
}
|
||||
}
|
||||
|
||||
export const scheduleMandatoryReminder = withReporting(
|
||||
_scheduleMandatoryReminder,
|
||||
"scheduleMandatoryReminder"
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import defaultPrisma from "@calcom/prisma";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
@@ -80,4 +81,4 @@ const getWebhooks = async (options: GetSubscriberOptions, prisma: PrismaClient =
|
||||
return allWebhooks;
|
||||
};
|
||||
|
||||
export default getWebhooks;
|
||||
export default withReporting(getWebhooks, "getWebhooks");
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
|
||||
import { getHumanReadableLocationValue } from "@calcom/lib/location";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { ApiKey } from "@calcom/prisma/client";
|
||||
@@ -304,7 +305,7 @@ export async function scheduleTrigger({
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteWebhookScheduledTriggers({
|
||||
async function _deleteWebhookScheduledTriggers({
|
||||
booking,
|
||||
appId,
|
||||
triggerEvent,
|
||||
@@ -361,6 +362,11 @@ export async function deleteWebhookScheduledTriggers({
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteWebhookScheduledTriggers = withReporting(
|
||||
_deleteWebhookScheduledTriggers,
|
||||
"deleteWebhookScheduledTriggers"
|
||||
);
|
||||
|
||||
export async function updateTriggerForExistingBookings(
|
||||
webhook: Webhook,
|
||||
existingEventTriggers: WebhookTriggerEvents[],
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
getNormalizedHostsWithDelegationCredentials,
|
||||
} from "@calcom/lib/bookings/getRoutedUsers";
|
||||
import type { EventType } from "@calcom/lib/bookings/getRoutedUsers";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
import type { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential";
|
||||
@@ -46,7 +47,7 @@ const isFixedHost = <T extends { isFixed: boolean }>(host: T): host is T & { isF
|
||||
return host.isFixed;
|
||||
};
|
||||
|
||||
export const findQualifiedHostsWithDelegationCredentials = async <
|
||||
const _findQualifiedHostsWithDelegationCredentials = async <
|
||||
T extends {
|
||||
email: string;
|
||||
id: number;
|
||||
@@ -206,3 +207,8 @@ export const findQualifiedHostsWithDelegationCredentials = async <
|
||||
fixedHosts,
|
||||
};
|
||||
};
|
||||
|
||||
export const findQualifiedHostsWithDelegationCredentials = withReporting(
|
||||
_findQualifiedHostsWithDelegationCredentials,
|
||||
"findQualifiedHostsWithDelegationCredentials"
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { intervalLimitKeyToUnit } from "@calcom/lib/intervalLimits/intervalLimit
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getPiiFreeBooking } from "@calcom/lib/piiFreeData";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
@@ -19,7 +20,7 @@ import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes";
|
||||
import { BookingRepository as BookingRepo } from "./server/repository/booking";
|
||||
|
||||
export async function getBusyTimes(params: {
|
||||
const _getBusyTimes = async (params: {
|
||||
credentials: CredentialForCalendarService[];
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
@@ -46,7 +47,7 @@ export async function getBusyTimes(params: {
|
||||
| null;
|
||||
bypassBusyCalendarTimes: boolean;
|
||||
shouldServeCache?: boolean;
|
||||
}) {
|
||||
}) => {
|
||||
const {
|
||||
credentials,
|
||||
userId,
|
||||
@@ -244,7 +245,9 @@ export async function getBusyTimes(params: {
|
||||
})
|
||||
);
|
||||
return busyTimes;
|
||||
}
|
||||
};
|
||||
|
||||
export const getBusyTimes = withReporting(_getBusyTimes, "getBusyTimes");
|
||||
|
||||
export function getStartEndDateforLimitCheck(
|
||||
startDate: string,
|
||||
@@ -359,4 +362,4 @@ export async function getBusyTimesForLimitChecks(params: {
|
||||
return busyTimes;
|
||||
}
|
||||
|
||||
export default getBusyTimes;
|
||||
export default withReporting(_getBusyTimes, "getBusyTimes");
|
||||
|
||||
@@ -35,7 +35,7 @@ import type { EventBusyDetails, IntervalLimitUnit } from "@calcom/types/Calendar
|
||||
import type { TimeRange } from "@calcom/types/schedule";
|
||||
|
||||
import { getBusyTimes } from "./getBusyTimes";
|
||||
import monitorCallbackAsync, { monitorCallbackSync } from "./sentryWrapper";
|
||||
import { withReporting } from "./sentryWrapper";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["getUserAvailability"] });
|
||||
const availabilitySchema = z
|
||||
@@ -55,12 +55,6 @@ const availabilitySchema = z
|
||||
})
|
||||
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
|
||||
|
||||
const getEventType = async (
|
||||
...args: Parameters<typeof _getEventType>
|
||||
): Promise<ReturnType<typeof _getEventType>> => {
|
||||
return monitorCallbackAsync(_getEventType, ...args);
|
||||
};
|
||||
|
||||
const _getEventType = async (id: number) => {
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: { id },
|
||||
@@ -150,17 +144,17 @@ const _getEventType = async (id: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
export type EventType = Awaited<ReturnType<typeof getEventType>>;
|
||||
export type EventType = Awaited<ReturnType<typeof _getEventType>>;
|
||||
|
||||
const getUser = async (...args: Parameters<typeof _getUser>): Promise<ReturnType<typeof _getUser>> => {
|
||||
return monitorCallbackAsync(_getUser, ...args);
|
||||
};
|
||||
export const getEventType = withReporting(_getEventType, "getEventType");
|
||||
|
||||
const _getUser = async (where: Prisma.UserWhereInput) => {
|
||||
return findUsersForAvailabilityCheck({ where });
|
||||
};
|
||||
|
||||
type GetUser = Awaited<ReturnType<typeof getUser>>;
|
||||
type GetUser = Awaited<ReturnType<typeof _getUser>>;
|
||||
|
||||
const getUser = withReporting(_getUser, "getUser");
|
||||
|
||||
export type GetUserAvailabilityInitialData = {
|
||||
user?: GetUser;
|
||||
@@ -213,12 +207,6 @@ type GetUserAvailabilityQuery = {
|
||||
shouldServeCache?: boolean;
|
||||
};
|
||||
|
||||
export const getCurrentSeats = async (
|
||||
...args: Parameters<typeof _getCurrentSeats>
|
||||
): Promise<ReturnType<typeof _getCurrentSeats>> => {
|
||||
return monitorCallbackAsync(_getCurrentSeats, ...args);
|
||||
};
|
||||
|
||||
const _getCurrentSeats = async (
|
||||
eventType: {
|
||||
id?: number;
|
||||
@@ -274,13 +262,9 @@ const _getCurrentSeats = async (
|
||||
});
|
||||
};
|
||||
|
||||
export type CurrentSeats = Awaited<ReturnType<typeof getCurrentSeats>>;
|
||||
export type CurrentSeats = Awaited<ReturnType<typeof _getCurrentSeats>>;
|
||||
|
||||
export const getUserAvailability = async (
|
||||
...args: Parameters<typeof _getUserAvailability>
|
||||
): Promise<GetUserAvailabilityResult> => {
|
||||
return monitorCallbackAsync(_getUserAvailability, ...args);
|
||||
};
|
||||
export const getCurrentSeats = withReporting(_getCurrentSeats, "getCurrentSeats");
|
||||
|
||||
type GetUserAvailabilityResult = ReturnType<typeof _getUserAvailability>;
|
||||
|
||||
@@ -416,7 +400,7 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
? EventTypeRepository.getSelectedCalendarsFromUser({ user, eventTypeId: eventType.id })
|
||||
: user.userLevelSelectedCalendars;
|
||||
|
||||
const busyTimes = await monitorCallbackAsync(getBusyTimes, {
|
||||
const busyTimes = await getBusyTimes({
|
||||
credentials: user.credentials,
|
||||
startTime: getBusyTimesStart,
|
||||
endTime: getBusyTimesEnd,
|
||||
@@ -613,18 +597,14 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getPeriodStartDatesBetween = (
|
||||
...args: Parameters<typeof _getPeriodStartDatesBetween>
|
||||
): ReturnType<typeof _getPeriodStartDatesBetween> => {
|
||||
return monitorCallbackSync(_getPeriodStartDatesBetween, ...args);
|
||||
};
|
||||
export const getUserAvailability = withReporting(_getUserAvailability, "getUserAvailability");
|
||||
|
||||
const _getPeriodStartDatesBetween = (
|
||||
dateFrom: Dayjs,
|
||||
dateTo: Dayjs,
|
||||
period: IntervalLimitUnit,
|
||||
timeZone?: string
|
||||
) => {
|
||||
): Dayjs[] => {
|
||||
const dates = [];
|
||||
let startDate = timeZone ? dayjs(dateFrom).tz(timeZone).startOf(period) : dayjs(dateFrom).startOf(period);
|
||||
const endDate = timeZone ? dayjs(dateTo).tz(timeZone).endOf(period) : dayjs(dateTo).endOf(period);
|
||||
@@ -636,6 +616,11 @@ const _getPeriodStartDatesBetween = (
|
||||
return dates;
|
||||
};
|
||||
|
||||
export const getPeriodStartDatesBetween = withReporting(
|
||||
_getPeriodStartDatesBetween,
|
||||
"getPeriodStartDatesBetween"
|
||||
);
|
||||
|
||||
interface GetUserAvailabilityParamsDTO {
|
||||
availability: (DateOverride | WorkingHours)[];
|
||||
}
|
||||
@@ -735,8 +720,4 @@ const _getUsersAvailability = async ({ users, query, initialData }: GetUsersAvai
|
||||
);
|
||||
};
|
||||
|
||||
export const getUsersAvailability = async (
|
||||
...args: Parameters<typeof _getUsersAvailability>
|
||||
): Promise<Awaited<GetUserAvailabilityResult>[]> => {
|
||||
return monitorCallbackAsync(_getUsersAvailability, ...args);
|
||||
};
|
||||
export const getUsersAvailability = withReporting(_getUsersAvailability, "getUsersAvailability");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { BookingRepository } from "@calcom/lib/server/repository/booking";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
@@ -9,7 +10,7 @@ import { ascendingLimitKeys, intervalLimitKeyToUnit } from "../intervalLimit";
|
||||
import type { IntervalLimit, IntervalLimitKey } from "../intervalLimitSchema";
|
||||
import { parseBookingLimit } from "../isBookingLimits";
|
||||
|
||||
export async function checkBookingLimits(
|
||||
async function _checkBookingLimits(
|
||||
bookingLimits: IntervalLimit,
|
||||
eventStartDate: Date,
|
||||
eventId: number,
|
||||
@@ -40,7 +41,9 @@ export async function checkBookingLimits(
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkBookingLimit({
|
||||
export const checkBookingLimits = withReporting(_checkBookingLimits, "checkBookingLimits");
|
||||
|
||||
async function _checkBookingLimit({
|
||||
eventStartDate,
|
||||
eventId,
|
||||
key,
|
||||
@@ -110,3 +113,5 @@ export async function checkBookingLimit({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const checkBookingLimit = withReporting(_checkBookingLimit, "checkBookingLimit");
|
||||
|
||||
@@ -3,7 +3,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { getStartEndDateforLimitCheck } from "@calcom/lib/getBusyTimes";
|
||||
import type { EventType } from "@calcom/lib/getUserAvailability";
|
||||
import { getPeriodStartDatesBetween } from "@calcom/lib/getUserAvailability";
|
||||
import monitorCallbackAsync from "@calcom/lib/sentryWrapper";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import { getTotalBookingDuration } from "@calcom/lib/server/queries";
|
||||
import { BookingRepository } from "@calcom/lib/server/repository/booking";
|
||||
@@ -15,12 +15,6 @@ import LimitManager from "../limitManager";
|
||||
import { isBookingWithinPeriod } from "../utils";
|
||||
import { checkBookingLimit } from "./checkBookingLimits";
|
||||
|
||||
export const getBusyTimesFromLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromLimits = async (
|
||||
bookingLimits: IntervalLimit | null,
|
||||
durationLimits: IntervalLimit | null,
|
||||
@@ -78,12 +72,6 @@ const _getBusyTimesFromLimits = async (
|
||||
return limitManager.getBusyTimes();
|
||||
};
|
||||
|
||||
const getBusyTimesFromBookingLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromBookingLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromBookingLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromBookingLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromBookingLimits = async (params: {
|
||||
bookings: EventBusyDetails[];
|
||||
bookingLimits: IntervalLimit;
|
||||
@@ -161,13 +149,6 @@ const _getBusyTimesFromBookingLimits = async (params: {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getBusyTimesFromDurationLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromDurationLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromDurationLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromDurationLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromDurationLimits = async (
|
||||
bookings: EventBusyDetails[],
|
||||
durationLimits: IntervalLimit,
|
||||
@@ -231,11 +212,10 @@ const _getBusyTimesFromDurationLimits = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getBusyTimesFromTeamLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromTeamLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromTeamLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromTeamLimits, ...args);
|
||||
};
|
||||
const getBusyTimesFromDurationLimits = withReporting(
|
||||
_getBusyTimesFromDurationLimits,
|
||||
"getBusyTimesFromDurationLimits"
|
||||
);
|
||||
|
||||
const _getBusyTimesFromTeamLimits = async (
|
||||
user: { id: number; email: string },
|
||||
@@ -287,3 +267,15 @@ const _getBusyTimesFromTeamLimits = async (
|
||||
|
||||
return limitManager.getBusyTimes();
|
||||
};
|
||||
|
||||
export const getBusyTimesFromLimits = withReporting(_getBusyTimesFromLimits, "getBusyTimesFromLimits");
|
||||
|
||||
export const getBusyTimesFromBookingLimits = withReporting(
|
||||
_getBusyTimesFromBookingLimits,
|
||||
"getBusyTimesFromBookingLimits"
|
||||
);
|
||||
|
||||
export const getBusyTimesFromTeamLimits = withReporting(
|
||||
_getBusyTimesFromTeamLimits,
|
||||
"getBusyTimesFromTeamLimits"
|
||||
);
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { redactSensitiveData } from "./redactSensitiveData";
|
||||
|
||||
describe("redactSensitiveData", () => {
|
||||
it("should return non-object values as is", () => {
|
||||
expect(redactSensitiveData(null)).toMatchInlineSnapshot(`null`);
|
||||
expect(redactSensitiveData(undefined)).toMatchInlineSnapshot(`undefined`);
|
||||
expect(redactSensitiveData("string")).toMatchInlineSnapshot(`"string"`);
|
||||
expect(redactSensitiveData(123)).toMatchInlineSnapshot(`123`);
|
||||
expect(redactSensitiveData(true)).toMatchInlineSnapshot(`true`);
|
||||
});
|
||||
|
||||
it("should redact sensitive fields from flat objects", () => {
|
||||
const input = {
|
||||
username: "test",
|
||||
password: "secret123",
|
||||
apiKey: "key123",
|
||||
token: "token123",
|
||||
normalField: "value",
|
||||
};
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"apiKey": "[REDACTED]",
|
||||
"normalField": "value",
|
||||
"password": "[REDACTED]",
|
||||
"token": "[REDACTED]",
|
||||
"username": "test",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should redact sensitive fields from nested objects", () => {
|
||||
const input = {
|
||||
user: {
|
||||
name: "John",
|
||||
password: "secret123",
|
||||
credentials: {
|
||||
apiKey: "key123",
|
||||
token: "token123",
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
theme: "dark",
|
||||
auth: {
|
||||
secret: "secret123",
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"settings": {
|
||||
"auth": "[REDACTED]",
|
||||
"theme": "dark",
|
||||
},
|
||||
"user": {
|
||||
"credentials": {
|
||||
"apiKey": "[REDACTED]",
|
||||
"token": "[REDACTED]",
|
||||
},
|
||||
"name": "John",
|
||||
"password": "[REDACTED]",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should handle arrays of objects", () => {
|
||||
const input = {
|
||||
users: [
|
||||
{ name: "John", password: "pass1", token: "token1" },
|
||||
{ name: "Jane", password: "pass2", token: "token2" },
|
||||
],
|
||||
config: {
|
||||
apiKey: "key123",
|
||||
items: [
|
||||
{ id: 1, secret: "secret1" },
|
||||
{ id: 2, secret: "secret2" },
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"config": {
|
||||
"apiKey": "[REDACTED]",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"secret": "[REDACTED]",
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"secret": "[REDACTED]",
|
||||
},
|
||||
],
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"name": "John",
|
||||
"password": "[REDACTED]",
|
||||
"token": "[REDACTED]",
|
||||
},
|
||||
{
|
||||
"name": "Jane",
|
||||
"password": "[REDACTED]",
|
||||
"token": "[REDACTED]",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should handle all sensitive field variations", () => {
|
||||
const input = {
|
||||
accessToken: "token1",
|
||||
api_key: "key1",
|
||||
apiKey: "key2",
|
||||
auth: "auth1",
|
||||
authorization: "auth2",
|
||||
client_id: "id1",
|
||||
client_secret: "secret1",
|
||||
credential: "cred1",
|
||||
hash: "hash1",
|
||||
key: "key3",
|
||||
password: "pass1",
|
||||
refreshToken: "token2",
|
||||
secret: "secret2",
|
||||
token: "token3",
|
||||
normalField: "value",
|
||||
};
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"accessToken": "[REDACTED]",
|
||||
"apiKey": "[REDACTED]",
|
||||
"api_key": "[REDACTED]",
|
||||
"auth": "[REDACTED]",
|
||||
"authorization": "[REDACTED]",
|
||||
"client_id": "[REDACTED]",
|
||||
"client_secret": "[REDACTED]",
|
||||
"credential": "[REDACTED]",
|
||||
"hash": "[REDACTED]",
|
||||
"key": "[REDACTED]",
|
||||
"normalField": "value",
|
||||
"password": "[REDACTED]",
|
||||
"refreshToken": "[REDACTED]",
|
||||
"secret": "[REDACTED]",
|
||||
"token": "[REDACTED]",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should handle circular references gracefully", () => {
|
||||
const input: any = { name: "test" };
|
||||
input.self = input;
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"name": "test",
|
||||
"self": "[Circular]",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should handle invalid JSON data gracefully", () => {
|
||||
const input = {
|
||||
name: "test",
|
||||
invalid: {
|
||||
toJSON: () => {
|
||||
throw new Error("Invalid JSON");
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(redactSensitiveData(input)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": "Redaction failed",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import logger from "./logger";
|
||||
|
||||
// Fields that might contain sensitive data
|
||||
const SENSITIVE_FIELDS = [
|
||||
"accessToken",
|
||||
"api_key",
|
||||
"apiKey",
|
||||
"auth",
|
||||
"authorization",
|
||||
"client_id",
|
||||
"client_secret",
|
||||
"credential",
|
||||
"hash",
|
||||
"key",
|
||||
"password",
|
||||
"refreshToken",
|
||||
"secret",
|
||||
"token",
|
||||
];
|
||||
|
||||
// Create a Set for efficient O(1) average time complexity lookups
|
||||
const SENSITIVE_FIELDS_SET = new Set<string>(SENSITIVE_FIELDS);
|
||||
|
||||
/**
|
||||
* Creates a replacer function for JSON.stringify that redacts sensitive fields.
|
||||
* This approach is more efficient as it leverages native JSON serialization.
|
||||
*/
|
||||
function createReplacer(): (key: string, value: unknown) => unknown {
|
||||
// Use a WeakSet to track circular references
|
||||
const seen = new WeakSet();
|
||||
|
||||
return function replacer(key: string, value: unknown): unknown {
|
||||
// Handle circular references
|
||||
if (value && typeof value === "object") {
|
||||
if (seen.has(value)) {
|
||||
return "[Circular]";
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
|
||||
// Redact sensitive fields
|
||||
if (SENSITIVE_FIELDS_SET.has(key)) {
|
||||
return "[REDACTED]";
|
||||
}
|
||||
|
||||
// Handle Error objects specially to preserve their structure
|
||||
if (value instanceof Error) {
|
||||
const errorObj: Record<string, unknown> = {
|
||||
name: value.name,
|
||||
message: value.message,
|
||||
};
|
||||
|
||||
if (value.stack) {
|
||||
errorObj.stack = value.stack;
|
||||
}
|
||||
|
||||
// Handle cause property safely
|
||||
if ("cause" in value && value.cause !== undefined) {
|
||||
errorObj.cause = value.cause;
|
||||
}
|
||||
|
||||
return errorObj;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redacts sensitive data from an object by replacing sensitive fields with a placeholder.
|
||||
* Uses JSON.stringify's replacer function for efficient serialization and redaction.
|
||||
*/
|
||||
export function redactSensitiveData(data: unknown): unknown {
|
||||
if (!data || typeof data !== "object") return data;
|
||||
|
||||
try {
|
||||
const replacer = createReplacer();
|
||||
const jsonString = JSON.stringify(data, replacer);
|
||||
return JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
logger.error("Failed to redact sensitive data", error);
|
||||
return { error: "Redaction failed" };
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { startSpan, captureException } from "@sentry/nextjs";
|
||||
|
||||
import { redactSensitiveData } from "./redactSensitiveData";
|
||||
|
||||
/*
|
||||
WHEN TO USE
|
||||
We ran a script that performs a simple mathematical calculation within a loop of 1000000 iterations.
|
||||
@@ -10,6 +12,10 @@ For smaller loops, the cost incurred may not be very significant on an absolute
|
||||
considering that a million monitored iterations only took roughly 8 seconds when monitored.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated Use withReporting instead. This function will be removed in a future version.
|
||||
* Example: withReporting(myFunction, "myFunction")(...args)
|
||||
*/
|
||||
const monitorCallbackAsync = async <T extends (...args: any[]) => any>(
|
||||
cb: T,
|
||||
...args: Parameters<T>
|
||||
@@ -30,6 +36,10 @@ const monitorCallbackAsync = async <T extends (...args: any[]) => any>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use withReporting instead. This function will be removed in a future version.
|
||||
* Example: withReporting(myFunction, "myFunction")(...args)
|
||||
*/
|
||||
const monitorCallbackSync = <T extends (...args: any[]) => any>(
|
||||
cb: T,
|
||||
...args: Parameters<T>
|
||||
@@ -49,5 +59,84 @@ const monitorCallbackSync = <T extends (...args: any[]) => any>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard to check if a value is a Promise
|
||||
*/
|
||||
function isPromise<T>(value: unknown): value is Promise<T> {
|
||||
return value instanceof Promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error handler that captures the error in Sentry with context
|
||||
*/
|
||||
function createErrorHandler(name: string, args: unknown[]) {
|
||||
return (error: unknown) => {
|
||||
const context = {
|
||||
functionName: name,
|
||||
args: redactSensitiveData(args),
|
||||
};
|
||||
|
||||
if (error instanceof Error) {
|
||||
(error as any).context = context;
|
||||
}
|
||||
|
||||
captureException(error, {
|
||||
extra: context,
|
||||
tags: { functionName: name },
|
||||
});
|
||||
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommended way to wrap functions for Sentry reporting.
|
||||
* Properly handles both sync and async functions with proper error context capture.
|
||||
*
|
||||
* @example
|
||||
* // Async function
|
||||
* const myAsyncFunction = withReporting(async (arg1: string, arg2: number) => {
|
||||
* // async implementation
|
||||
* }, "myAsyncFunction");
|
||||
*
|
||||
* // Sync function
|
||||
* const mySyncFunction = withReporting((arg1: string, arg2: number) => {
|
||||
* // sync implementation
|
||||
* }, "mySyncFunction");
|
||||
*/
|
||||
export function withReporting<T extends any[], R>(func: (...args: T) => R, name: string): (...args: T) => R {
|
||||
if (!name?.trim()) {
|
||||
throw new Error("withReporting requires a non-empty name parameter");
|
||||
}
|
||||
|
||||
// Early return if Sentry is not configured
|
||||
if (!process.env.NEXT_PUBLIC_SENTRY_DSN || !process.env.SENTRY_TRACES_SAMPLE_RATE) {
|
||||
return func;
|
||||
}
|
||||
|
||||
return (...args: T): R => {
|
||||
return startSpan({ name }, () => {
|
||||
const onError = createErrorHandler(name, args);
|
||||
|
||||
try {
|
||||
const result = func(...args);
|
||||
|
||||
// Handle Promise case with proper type guard
|
||||
if (isPromise(result)) {
|
||||
return result.catch(onError) as R;
|
||||
}
|
||||
|
||||
// Handle sync case
|
||||
return result;
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
// This line is technically unreachable due to onError throwing,
|
||||
// but TypeScript needs it for type safety
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default monitorCallbackAsync;
|
||||
export { monitorCallbackSync };
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { FormResponse } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import monitorCallbackAsync from "@calcom/lib/sentryWrapper";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
|
||||
@@ -36,10 +36,6 @@ function hasEmbedPath(pathWithQuery: string) {
|
||||
return onlyPath.endsWith("/embed") || onlyPath.endsWith("/embed/");
|
||||
}
|
||||
|
||||
export const getRoutedUrl = (context: Pick<GetServerSidePropsContext, "query" | "req">) => {
|
||||
return monitorCallbackAsync(_getRoutedUrl, context);
|
||||
};
|
||||
|
||||
const _getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "query" | "req">) => {
|
||||
const queryParsed = querySchema.safeParse(context.query);
|
||||
const isEmbed = hasEmbedPath(context.req.url || "");
|
||||
@@ -221,3 +217,5 @@ const _getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "query" |
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getRoutedUrl = withReporting(_getRoutedUrl, "getRoutedUrl");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import type { FormResponse } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { Booking } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
@@ -165,7 +166,7 @@ export class BookingRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findAllExistingBookingsForEventTypeBetween({
|
||||
private static async _findAllExistingBookingsForEventTypeBetween({
|
||||
eventTypeId,
|
||||
seatedEvent = false,
|
||||
startDate,
|
||||
@@ -277,6 +278,11 @@ export class BookingRepository {
|
||||
return [...resultOne, ...resultTwoWithOrganizersRemoved, ...resultThree];
|
||||
}
|
||||
|
||||
static findAllExistingBookingsForEventTypeBetween = withReporting(
|
||||
BookingRepository._findAllExistingBookingsForEventTypeBetween,
|
||||
"findAllExistingBookingsForEventTypeBetween"
|
||||
);
|
||||
|
||||
static async getAllBookingsForRoundRobin({
|
||||
users,
|
||||
eventTypeId,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { IFromUser, IOutOfOfficeData, IToUser } from "@calcom/lib/getUserAvailability";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
|
||||
import type { DateRange } from "./date-ranges";
|
||||
import { getTimeZone } from "./dayjs";
|
||||
@@ -168,4 +169,4 @@ const getSlots = ({
|
||||
});
|
||||
};
|
||||
|
||||
export default getSlots;
|
||||
export default withReporting(getSlots, "getSlots");
|
||||
|
||||
@@ -18,19 +18,19 @@ import { getAggregatedAvailability } from "@calcom/lib/getAggregatedAvailability
|
||||
import { getBusyTimesForLimitChecks, getStartEndDateforLimitCheck } from "@calcom/lib/getBusyTimes";
|
||||
import type {
|
||||
CurrentSeats,
|
||||
EventType,
|
||||
GetAvailabilityUser,
|
||||
IFromUser,
|
||||
IToUser,
|
||||
EventType,
|
||||
} from "@calcom/lib/getUserAvailability";
|
||||
import { getUsersAvailability, getPeriodStartDatesBetween } from "@calcom/lib/getUserAvailability";
|
||||
import { getPeriodStartDatesBetween, getUsersAvailability } from "@calcom/lib/getUserAvailability";
|
||||
import { descendingLimitKeys, intervalLimitKeyToUnit } from "@calcom/lib/intervalLimits/intervalLimit";
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits";
|
||||
import { parseDurationLimit } from "@calcom/lib/intervalLimits/isDurationLimits";
|
||||
import LimitManager from "@calcom/lib/intervalLimits/limitManager";
|
||||
import { checkBookingLimit } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
|
||||
import { isBookingWithinPeriod, getUnitFromBusyTime } from "@calcom/lib/intervalLimits/utils";
|
||||
import { getUnitFromBusyTime, isBookingWithinPeriod } from "@calcom/lib/intervalLimits/utils";
|
||||
import {
|
||||
calculatePeriodLimits,
|
||||
isTimeOutOfBounds,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
} from "@calcom/lib/isOutOfBounds";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import monitorCallbackAsync, { monitorCallbackSync } from "@calcom/lib/sentryWrapper";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { getTotalBookingDuration } from "@calcom/lib/server/queries";
|
||||
import { BookingRepository as BookingRepo } from "@calcom/lib/server/repository/booking";
|
||||
import { EventTypeRepository } from "@calcom/lib/server/repository/eventType";
|
||||
@@ -49,7 +49,7 @@ import { PeriodType, Prisma } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { EventBusyDate, EventBusyDetails } from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload, CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -158,10 +158,10 @@ async function _getReservedSlotsAndCleanupExpired({
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEventType(
|
||||
const _getEventType = async (
|
||||
input: TGetScheduleInputSchema,
|
||||
organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean }
|
||||
) {
|
||||
) => {
|
||||
const { eventTypeSlug, usernameList, isTeamEvent } = input;
|
||||
log.info("getEventType", safeStringify({ usernameList, eventTypeSlug, isTeamEvent, organizationDetails }));
|
||||
const eventTypeId =
|
||||
@@ -179,12 +179,14 @@ export async function getEventType(
|
||||
}
|
||||
|
||||
return await EventTypeRepository.findForSlots({ id: eventTypeId });
|
||||
}
|
||||
};
|
||||
|
||||
export async function getDynamicEventType(
|
||||
export const getEventType = withReporting(_getEventType, "getEventType");
|
||||
|
||||
const _getDynamicEventType = async (
|
||||
input: TGetScheduleInputSchema,
|
||||
organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean }
|
||||
) {
|
||||
) => {
|
||||
const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
|
||||
// For dynamic booking, we need to get and update user credentials, schedule and availability in the eventTypeObject as they're required in the new availability logic
|
||||
if (!input.eventTypeSlug) {
|
||||
@@ -226,17 +228,24 @@ export async function getDynamicEventType(
|
||||
return Object.assign({}, dynamicEventType, {
|
||||
users,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export async function getRegularOrDynamicEventType(
|
||||
export const getDynamicEventType = withReporting(_getDynamicEventType, "getDynamicEventType");
|
||||
|
||||
const _getRegularOrDynamicEventType = async (
|
||||
input: TGetScheduleInputSchema,
|
||||
organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean }
|
||||
) {
|
||||
) => {
|
||||
const isDynamicBooking = input.usernameList && input.usernameList.length > 1;
|
||||
return isDynamicBooking
|
||||
? await getDynamicEventType(input, organizationDetails)
|
||||
: await getEventType(input, organizationDetails);
|
||||
}
|
||||
};
|
||||
|
||||
export const getRegularOrDynamicEventType = withReporting(
|
||||
_getRegularOrDynamicEventType,
|
||||
"getRegularOrDynamicEventType"
|
||||
);
|
||||
|
||||
function applyOccupiedSeatsToCurrentSeats(currentSeats: CurrentSeats, occupiedSeats: SelectedSlots[]) {
|
||||
const occupiedSeatsCount = countBy(occupiedSeats, (item) => item.slotUtcStartDate.toISOString());
|
||||
@@ -267,16 +276,18 @@ export interface IGetAvailableSlots {
|
||||
troubleshooter?: any;
|
||||
}
|
||||
|
||||
export function getUsersWithCredentials({
|
||||
const _getUsersWithCredentials = ({
|
||||
hosts,
|
||||
}: {
|
||||
hosts: {
|
||||
isFixed?: boolean;
|
||||
user: GetAvailabilityUserWithDelegationCredentials;
|
||||
}[];
|
||||
}) {
|
||||
}) => {
|
||||
return hosts.map(({ isFixed, user }) => ({ isFixed, ...user }));
|
||||
}
|
||||
};
|
||||
|
||||
export const getUsersWithCredentials = withReporting(_getUsersWithCredentials, "getUsersWithCredentials");
|
||||
|
||||
const getStartTime = (startTimeInput: string, timeZone?: string, minimumBookingNotice?: number) => {
|
||||
const startTimeMin = dayjs.utc().add(minimumBookingNotice || 1, "minutes");
|
||||
@@ -285,13 +296,7 @@ const getStartTime = (startTimeInput: string, timeZone?: string, minimumBookingN
|
||||
return startTimeMin.isAfter(startTime) ? startTimeMin.tz(timeZone) : startTime;
|
||||
};
|
||||
|
||||
export const getAvailableSlots = async (
|
||||
...args: Parameters<typeof _getAvailableSlots>
|
||||
): Promise<ReturnType<typeof _getAvailableSlots>> => {
|
||||
return monitorCallbackAsync(_getAvailableSlots, ...args);
|
||||
};
|
||||
|
||||
async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<IGetAvailableSlots> {
|
||||
const _getAvailableSlots = async ({ input, ctx }: GetScheduleOptions): Promise<IGetAvailableSlots> => {
|
||||
const {
|
||||
_enableTroubleshooter: enableTroubleshooter = false,
|
||||
_bypassCalendarBusyTimes: bypassBusyCalendarTimes = false,
|
||||
@@ -309,7 +314,7 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
logger.settings.minLevel = 2;
|
||||
}
|
||||
|
||||
const eventType = await monitorCallbackAsync(getRegularOrDynamicEventType, input, orgDetails);
|
||||
const eventType = await getRegularOrDynamicEventType(input, orgDetails);
|
||||
|
||||
if (!eventType) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
@@ -380,16 +385,14 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
});
|
||||
}
|
||||
|
||||
const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = await monitorCallbackAsync(
|
||||
findQualifiedHostsWithDelegationCredentials<GetAvailabilityUserWithoutDelegationCredentials>,
|
||||
{
|
||||
const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } =
|
||||
await findQualifiedHostsWithDelegationCredentials({
|
||||
eventType,
|
||||
rescheduleUid: input.rescheduleUid ?? null,
|
||||
routedTeamMemberIds,
|
||||
contactOwnerEmail,
|
||||
routingFormResponse,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const allHosts = [...qualifiedRRHosts, ...fixedHosts];
|
||||
|
||||
@@ -481,7 +484,7 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN ||
|
||||
allUsersAvailability.length > 1;
|
||||
|
||||
const timeSlots = monitorCallbackSync(getSlots, {
|
||||
const timeSlots = getSlots({
|
||||
inviteeDate: startTime,
|
||||
eventLength: input.duration || eventType.length,
|
||||
offsetStart: eventType.offsetStart,
|
||||
@@ -584,7 +587,7 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
timeZone: input.timeZone,
|
||||
});
|
||||
|
||||
const slotsMappedToDate = monitorCallbackSync(function mapSlotsToDate() {
|
||||
function _mapSlotsToDate() {
|
||||
return availableTimeSlots.reduce(
|
||||
(
|
||||
r: Record<string, { time: string; attendees?: number; bookingUid?: string }[]>,
|
||||
@@ -597,7 +600,7 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
const dateString = formatter.format(time.toDate());
|
||||
|
||||
r[dateString] = r[dateString] || [];
|
||||
if (eventType.onlyShowFirstAvailableSlot && r[dateString].length > 0) {
|
||||
if (eventType?.onlyShowFirstAvailableSlot && r[dateString].length > 0) {
|
||||
return r;
|
||||
}
|
||||
r[dateString].push({
|
||||
@@ -619,12 +622,14 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
},
|
||||
Object.create(null)
|
||||
);
|
||||
});
|
||||
}
|
||||
const mapSlotsToDate = withReporting(_mapSlotsToDate, "mapSlotsToDate");
|
||||
const slotsMappedToDate = mapSlotsToDate();
|
||||
|
||||
loggerWithEventDetails.debug({ slotsMappedToDate });
|
||||
|
||||
const availableDates = Object.keys(slotsMappedToDate);
|
||||
const allDatesWithBookabilityStatus = monitorCallbackSync(getAllDatesWithBookabilityStatus, availableDates);
|
||||
const allDatesWithBookabilityStatus = getAllDatesWithBookabilityStatus(availableDates);
|
||||
loggerWithEventDetails.debug({ availableDates });
|
||||
|
||||
// timeZone isn't directly set on eventType now(So, it is legacy)
|
||||
@@ -646,7 +651,9 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
bookerUtcOffset,
|
||||
});
|
||||
let foundAFutureLimitViolation = false;
|
||||
const withinBoundsSlotsMappedToDate = monitorCallbackSync(function mapWithinBoundsSlotsToDate() {
|
||||
function _mapWithinBoundsSlotsToDate() {
|
||||
// This should never happen. Just for type safety, we already check in the upper scope
|
||||
if (!eventType) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
return Object.entries(slotsMappedToDate).reduce((withinBoundsSlotsMappedToDate, [date, slots]) => {
|
||||
// Computation Optimization: If a future limit violation has been found, we just consider all slots to be out of bounds beyond that slot.
|
||||
// We can't do the same for periodType=RANGE because it can start from a day other than today and today will hit the violation then.
|
||||
@@ -676,7 +683,9 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
withinBoundsSlotsMappedToDate[date] = filteredSlots;
|
||||
return withinBoundsSlotsMappedToDate;
|
||||
}, {} as typeof slotsMappedToDate);
|
||||
});
|
||||
}
|
||||
const mapWithinBoundsSlotsToDate = withReporting(_mapWithinBoundsSlotsToDate, "mapWithinBoundsSlotsToDate");
|
||||
const withinBoundsSlotsMappedToDate = mapWithinBoundsSlotsToDate();
|
||||
|
||||
// We only want to run this on single targeted events and not dynamic
|
||||
if (!Object.keys(withinBoundsSlotsMappedToDate).length && input.usernameList?.length === 1) {
|
||||
@@ -724,7 +733,9 @@ async function _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
slots: withinBoundsSlotsMappedToDate,
|
||||
...troubleshooterData,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getAvailableSlots = withReporting(_getAvailableSlots, "getAvailableSlots");
|
||||
|
||||
function doesRangeStartFromToday(periodType: PeriodType) {
|
||||
return periodType === PeriodType.ROLLING_WINDOW || periodType === PeriodType.ROLLING;
|
||||
@@ -760,7 +771,7 @@ async function getTeamIdFromSlug(
|
||||
return team?.id;
|
||||
}
|
||||
|
||||
async function getOOODates(startTimeDate: Date, endTimeDate: Date, allUserIds: number[]) {
|
||||
const _getOOODates = async (startTimeDate: Date, endTimeDate: Date, allUserIds: number[]) => {
|
||||
return await prisma.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
@@ -827,9 +838,11 @@ async function getOOODates(startTimeDate: Date, endTimeDate: Date, allUserIds: n
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function getAllDatesWithBookabilityStatus(availableDates: string[]) {
|
||||
export const getOOODates = withReporting(_getOOODates, "getOOODates");
|
||||
|
||||
const _getAllDatesWithBookabilityStatus = (availableDates: string[]) => {
|
||||
const availableDatesSet = new Set(availableDates);
|
||||
const firstDate = dayjs(availableDates[0]);
|
||||
const lastDate = dayjs(availableDates[availableDates.length - 1]);
|
||||
@@ -844,12 +857,14 @@ export function getAllDatesWithBookabilityStatus(availableDates: string[]) {
|
||||
currentDate = currentDate.add(1, "day");
|
||||
}
|
||||
return allDates;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets busy times from limits for multiple users
|
||||
*/
|
||||
const getBusyTimesFromLimitsForUsers = async (
|
||||
export const getAllDatesWithBookabilityStatus = withReporting(
|
||||
_getAllDatesWithBookabilityStatus,
|
||||
"getAllDatesWithBookabilityStatus"
|
||||
);
|
||||
|
||||
const _getBusyTimesFromLimitsForUsers = async (
|
||||
users: { id: number; email: string }[],
|
||||
bookingLimits: IntervalLimit | null,
|
||||
durationLimits: IntervalLimit | null,
|
||||
@@ -925,112 +940,109 @@ const getBusyTimesFromLimitsForUsers = async (
|
||||
limitManager.addBusyTime(start, unit, timeZone);
|
||||
}
|
||||
|
||||
await monitorCallbackAsync(async () => {
|
||||
if (bookingLimits) {
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = bookingLimits?.[key];
|
||||
if (!limit) continue;
|
||||
if (bookingLimits) {
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = bookingLimits?.[key];
|
||||
if (!limit) continue;
|
||||
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
|
||||
if (unit === "year") {
|
||||
try {
|
||||
await checkBookingLimit({
|
||||
eventStartDate: periodStart.toDate(),
|
||||
limitingNumber: limit,
|
||||
eventId: eventType.id,
|
||||
key,
|
||||
user,
|
||||
rescheduleUid,
|
||||
timeZone,
|
||||
});
|
||||
} catch (_) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (
|
||||
periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalBookings = 0;
|
||||
|
||||
for (const booking of userBookings) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalBookings++;
|
||||
if (totalBookings >= limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (durationLimits) {
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = durationLimits?.[key];
|
||||
if (!limit) continue;
|
||||
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
|
||||
const selectedDuration = (duration || eventType.length) ?? 0;
|
||||
|
||||
if (selectedDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unit === "year") {
|
||||
const totalYearlyDuration = await getTotalBookingDuration({
|
||||
if (unit === "year") {
|
||||
try {
|
||||
await checkBookingLimit({
|
||||
eventStartDate: periodStart.toDate(),
|
||||
limitingNumber: limit,
|
||||
eventId: eventType.id,
|
||||
startDate: periodStart.toDate(),
|
||||
endDate: periodStart.endOf(unit).toDate(),
|
||||
key,
|
||||
user,
|
||||
rescheduleUid,
|
||||
timeZone,
|
||||
});
|
||||
if (totalYearlyDuration + selectedDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (
|
||||
periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
} catch (_) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (
|
||||
periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalBookings = 0;
|
||||
|
||||
for (const booking of userBookings) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalDuration = selectedDuration;
|
||||
|
||||
for (const booking of userBookings) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalDuration += dayjs(booking.end).diff(dayjs(booking.start), "minute");
|
||||
if (totalDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
totalBookings++;
|
||||
if (totalBookings >= limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (durationLimits) {
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = durationLimits?.[key];
|
||||
if (!limit) continue;
|
||||
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
|
||||
const selectedDuration = (duration || eventType.length) ?? 0;
|
||||
|
||||
if (selectedDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unit === "year") {
|
||||
const totalYearlyDuration = await getTotalBookingDuration({
|
||||
eventId: eventType.id,
|
||||
startDate: periodStart.toDate(),
|
||||
endDate: periodStart.endOf(unit).toDate(),
|
||||
rescheduleUid,
|
||||
});
|
||||
if (totalYearlyDuration + selectedDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (
|
||||
periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalDuration = selectedDuration;
|
||||
|
||||
for (const booking of userBookings) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
totalDuration += dayjs(booking.end).diff(dayjs(booking.start), "minute");
|
||||
if (totalDuration > limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userBusyTimesMap.set(user.id, limitManager.getBusyTimes());
|
||||
}
|
||||
@@ -1038,10 +1050,12 @@ const getBusyTimesFromLimitsForUsers = async (
|
||||
return userBusyTimesMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets busy times from team booking limits for multiple users
|
||||
*/
|
||||
const getBusyTimesFromTeamLimitsForUsers = async (
|
||||
export const getBusyTimesFromLimitsForUsers = withReporting(
|
||||
_getBusyTimesFromLimitsForUsers,
|
||||
"getBusyTimesFromLimitsForUsers"
|
||||
);
|
||||
|
||||
const _getBusyTimesFromTeamLimitsForUsers = async (
|
||||
users: { id: number; email: string }[],
|
||||
bookingLimits: IntervalLimit,
|
||||
dateFrom: Dayjs,
|
||||
@@ -1116,71 +1130,66 @@ const getBusyTimesFromTeamLimitsForUsers = async (
|
||||
|
||||
limitManager.addBusyTime(start, unit, timeZone);
|
||||
}
|
||||
const bookingLimitsParams = {
|
||||
bookings: userBusyTimes,
|
||||
bookingLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
limitManager,
|
||||
rescheduleUid,
|
||||
teamId,
|
||||
user,
|
||||
includeManagedEvents,
|
||||
timeZone,
|
||||
};
|
||||
|
||||
await monitorCallbackAsync(async () => {
|
||||
const bookingLimitsParams = {
|
||||
bookings: userBusyTimes,
|
||||
bookingLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
limitManager,
|
||||
rescheduleUid,
|
||||
teamId,
|
||||
user,
|
||||
includeManagedEvents,
|
||||
timeZone,
|
||||
};
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = bookingLimits?.[key];
|
||||
if (!limit) continue;
|
||||
|
||||
for (const key of descendingLimitKeys) {
|
||||
const limit = bookingLimits?.[key];
|
||||
if (!limit) continue;
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
|
||||
const unit = intervalLimitKeyToUnit(key);
|
||||
const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
|
||||
for (const periodStart of periodStartDates) {
|
||||
if (limitManager.isAlreadyBusy(periodStart, unit, timeZone)) continue;
|
||||
|
||||
if (unit === "year") {
|
||||
try {
|
||||
await checkBookingLimit({
|
||||
eventStartDate: periodStart.toDate(),
|
||||
limitingNumber: limit,
|
||||
key,
|
||||
teamId,
|
||||
user,
|
||||
rescheduleUid,
|
||||
includeManagedEvents,
|
||||
timeZone,
|
||||
});
|
||||
} catch (_) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (
|
||||
periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (unit === "year") {
|
||||
try {
|
||||
await checkBookingLimit({
|
||||
eventStartDate: periodStart.toDate(),
|
||||
limitingNumber: limit,
|
||||
key,
|
||||
teamId,
|
||||
user,
|
||||
rescheduleUid,
|
||||
includeManagedEvents,
|
||||
timeZone,
|
||||
});
|
||||
} catch (_) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
if (periodStartDates.every((start: Dayjs) => limitManager.isAlreadyBusy(start, unit, timeZone))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalBookings = 0;
|
||||
|
||||
for (const booking of userBusyTimes) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const periodEnd = periodStart.endOf(unit);
|
||||
let totalBookings = 0;
|
||||
|
||||
for (const booking of userBusyTimes) {
|
||||
if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalBookings++;
|
||||
if (totalBookings >= limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
totalBookings++;
|
||||
if (totalBookings >= limit) {
|
||||
limitManager.addBusyTime(periodStart, unit, timeZone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
userBusyTimesMap.set(user.id, limitManager.getBusyTimes());
|
||||
}
|
||||
@@ -1188,6 +1197,11 @@ const getBusyTimesFromTeamLimitsForUsers = async (
|
||||
return userBusyTimesMap;
|
||||
};
|
||||
|
||||
export const getBusyTimesFromTeamLimitsForUsers = withReporting(
|
||||
_getBusyTimesFromTeamLimitsForUsers,
|
||||
"getBusyTimesFromTeamLimitsForUsers"
|
||||
);
|
||||
|
||||
const calculateHostsAndAvailabilities = async ({
|
||||
input,
|
||||
eventType,
|
||||
@@ -1210,7 +1224,7 @@ const calculateHostsAndAvailabilities = async ({
|
||||
bypassBusyCalendarTimes: boolean;
|
||||
shouldServeCache?: boolean;
|
||||
}) => {
|
||||
const usersWithCredentials = monitorCallbackSync(getUsersWithCredentials, {
|
||||
const usersWithCredentials = getUsersWithCredentials({
|
||||
hosts,
|
||||
});
|
||||
|
||||
@@ -1233,14 +1247,14 @@ const calculateHostsAndAvailabilities = async ({
|
||||
const allUserIds = Array.from(userIdAndEmailMap.keys());
|
||||
|
||||
const [currentBookingsAllUsers, outOfOfficeDaysAllUsers] = await Promise.all([
|
||||
monitorCallbackAsync(BookingRepo.findAllExistingBookingsForEventTypeBetween, {
|
||||
BookingRepo.findAllExistingBookingsForEventTypeBetween({
|
||||
startDate: startTimeDate,
|
||||
endDate: endTimeDate,
|
||||
eventTypeId: eventType.id,
|
||||
seatedEvent: Boolean(eventType.seatsPerTimeSlot),
|
||||
userIdAndEmailMap,
|
||||
}),
|
||||
monitorCallbackAsync(getOOODates, startTimeDate, endTimeDate, allUserIds),
|
||||
getOOODates(startTimeDate, endTimeDate, allUserIds),
|
||||
]);
|
||||
|
||||
const bookingLimits = parseBookingLimit(eventType?.bookingLimits);
|
||||
@@ -1248,7 +1262,7 @@ const calculateHostsAndAvailabilities = async ({
|
||||
let busyTimesFromLimitsBookingsAllUsers: Awaited<ReturnType<typeof getBusyTimesForLimitChecks>> = [];
|
||||
|
||||
if (eventType && (bookingLimits || durationLimits)) {
|
||||
busyTimesFromLimitsBookingsAllUsers = await monitorCallbackAsync(getBusyTimesForLimitChecks, {
|
||||
busyTimesFromLimitsBookingsAllUsers = await getBusyTimesForLimitChecks({
|
||||
userIds: allUserIds,
|
||||
eventTypeId: eventType.id,
|
||||
startDate: startTime.format(),
|
||||
@@ -1296,7 +1310,7 @@ const calculateHostsAndAvailabilities = async ({
|
||||
);
|
||||
}
|
||||
|
||||
const users = monitorCallbackSync(function enrichUsersWithData() {
|
||||
function _enrichUsersWithData() {
|
||||
return usersWithCredentials.map((currentUser) => {
|
||||
return {
|
||||
...currentUser,
|
||||
@@ -1311,7 +1325,9 @@ const calculateHostsAndAvailabilities = async ({
|
||||
outOfOfficeDays: outOfOfficeDaysAllUsers.filter((o) => o.user.id === currentUser.id),
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
const enrichUsersWithData = withReporting(_enrichUsersWithData, "enrichUsersWithData");
|
||||
const users = enrichUsersWithData();
|
||||
|
||||
const premappedUsersAvailability = await getUsersAvailability({
|
||||
users,
|
||||
|
||||
Reference in New Issue
Block a user