-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move functions to separate files (#15590)
* refactor: move functions to separate files * chore: remove inline function * chore: type error * fix: type error
- Loading branch information
1 parent
03311de
commit 3d06a2d
Showing
9 changed files
with
397 additions
and
319 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
62 changes: 62 additions & 0 deletions
62
packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { extractBaseEmail } from "@calcom/lib/extract-base-email"; | ||
import { HttpError } from "@calcom/lib/http-error"; | ||
import prisma from "@calcom/prisma"; | ||
|
||
export const checkIfBookerEmailIsBlocked = async ({ | ||
bookerEmail, | ||
loggedInUserId, | ||
}: { | ||
bookerEmail: string; | ||
loggedInUserId?: number; | ||
}) => { | ||
const baseEmail = extractBaseEmail(bookerEmail); | ||
const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS | ||
? process.env.BLACKLISTED_GUEST_EMAILS.split(",") | ||
: []; | ||
|
||
const blacklistedEmail = blacklistedGuestEmails.find( | ||
(guestEmail: string) => guestEmail.toLowerCase() === baseEmail.toLowerCase() | ||
); | ||
|
||
if (!blacklistedEmail) { | ||
return false; | ||
} | ||
|
||
const user = await prisma.user.findFirst({ | ||
where: { | ||
OR: [ | ||
{ | ||
email: baseEmail, | ||
emailVerified: { | ||
not: null, | ||
}, | ||
}, | ||
{ | ||
secondaryEmails: { | ||
some: { | ||
email: baseEmail, | ||
emailVerified: { | ||
not: null, | ||
}, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
select: { | ||
id: true, | ||
email: true, | ||
}, | ||
}); | ||
|
||
if (!user) { | ||
throw new HttpError({ statusCode: 403, message: "Cannot use this email to create the booking." }); | ||
} | ||
|
||
if (user.id !== loggedInUserId) { | ||
throw new HttpError({ | ||
statusCode: 403, | ||
message: `Attendee email has been blocked. Make sure to login as ${bookerEmail} to use this email for creating a booking.`, | ||
}); | ||
} | ||
}; |
129 changes: 129 additions & 0 deletions
129
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import type { LocationObject } from "@calcom/app-store/locations"; | ||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; | ||
import { parseRecurringEvent } from "@calcom/lib"; | ||
import prisma, { userSelect } from "@calcom/prisma"; | ||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; | ||
import { EventTypeMetaDataSchema, customInputSchema } from "@calcom/prisma/zod-utils"; | ||
|
||
export const getEventTypesFromDB = async (eventTypeId: number) => { | ||
const eventType = await prisma.eventType.findUniqueOrThrow({ | ||
where: { | ||
id: eventTypeId, | ||
}, | ||
select: { | ||
id: true, | ||
customInputs: true, | ||
disableGuests: true, | ||
users: { | ||
select: { | ||
credentials: { | ||
select: credentialForCalendarServiceSelect, | ||
}, | ||
...userSelect.select, | ||
}, | ||
}, | ||
slug: true, | ||
team: { | ||
select: { | ||
id: true, | ||
name: true, | ||
parentId: true, | ||
}, | ||
}, | ||
bookingFields: true, | ||
title: true, | ||
length: true, | ||
eventName: true, | ||
schedulingType: true, | ||
description: true, | ||
periodType: true, | ||
periodStartDate: true, | ||
periodEndDate: true, | ||
periodDays: true, | ||
periodCountCalendarDays: true, | ||
lockTimeZoneToggleOnBookingPage: true, | ||
requiresConfirmation: true, | ||
requiresBookerEmailVerification: true, | ||
minimumBookingNotice: true, | ||
userId: true, | ||
price: true, | ||
currency: true, | ||
metadata: true, | ||
destinationCalendar: true, | ||
hideCalendarNotes: true, | ||
seatsPerTimeSlot: true, | ||
recurringEvent: true, | ||
seatsShowAttendees: true, | ||
seatsShowAvailabilityCount: true, | ||
bookingLimits: true, | ||
durationLimits: true, | ||
assignAllTeamMembers: true, | ||
parentId: true, | ||
useEventTypeDestinationCalendarEmail: true, | ||
owner: { | ||
select: { | ||
hideBranding: true, | ||
}, | ||
}, | ||
workflows: { | ||
include: { | ||
workflow: { | ||
include: { | ||
steps: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
locations: true, | ||
timeZone: true, | ||
schedule: { | ||
select: { | ||
id: true, | ||
availability: true, | ||
timeZone: true, | ||
}, | ||
}, | ||
hosts: { | ||
select: { | ||
isFixed: true, | ||
priority: true, | ||
user: { | ||
select: { | ||
credentials: { | ||
select: credentialForCalendarServiceSelect, | ||
}, | ||
...userSelect.select, | ||
}, | ||
}, | ||
}, | ||
}, | ||
availability: { | ||
select: { | ||
date: true, | ||
startTime: true, | ||
endTime: true, | ||
days: true, | ||
}, | ||
}, | ||
secondaryEmailId: true, | ||
secondaryEmail: { | ||
select: { | ||
id: true, | ||
email: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
return { | ||
...eventType, | ||
metadata: EventTypeMetaDataSchema.parse(eventType?.metadata || {}), | ||
recurringEvent: parseRecurringEvent(eventType?.recurringEvent), | ||
customInputs: customInputSchema.array().parse(eventType?.customInputs || []), | ||
locations: (eventType?.locations ?? []) as LocationObject[], | ||
bookingFields: getBookingFieldsWithSystemFields(eventType || {}), | ||
isDynamic: false, | ||
}; | ||
}; | ||
|
||
export type getEventTypeResponse = Awaited<ReturnType<typeof getEventTypesFromDB>>; |
71 changes: 71 additions & 0 deletions
71
packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import dayjs from "@calcom/dayjs"; | ||
|
||
import type { getEventTypeResponse } from "./getEventTypesFromDB"; | ||
|
||
type EventType = Pick<getEventTypeResponse, "metadata" | "requiresConfirmation">; | ||
type PaymentAppData = { price: number }; | ||
|
||
export function getRequiresConfirmationFlags({ | ||
eventType, | ||
bookingStartTime, | ||
userId, | ||
paymentAppData, | ||
originalRescheduledBookingOrganizerId, | ||
}: { | ||
eventType: EventType; | ||
bookingStartTime: string; | ||
userId: number | undefined; | ||
paymentAppData: PaymentAppData; | ||
originalRescheduledBookingOrganizerId: number | undefined; | ||
}) { | ||
const requiresConfirmation = determineRequiresConfirmation(eventType, bookingStartTime); | ||
const userReschedulingIsOwner = isUserReschedulingOwner(userId, originalRescheduledBookingOrganizerId); | ||
const isConfirmedByDefault = determineIsConfirmedByDefault( | ||
requiresConfirmation, | ||
paymentAppData.price, | ||
userReschedulingIsOwner | ||
); | ||
|
||
return { | ||
/** | ||
* Organizer of the booking is rescheduling | ||
*/ | ||
userReschedulingIsOwner, | ||
/** | ||
* Booking won't need confirmation to be ACCEPTED | ||
*/ | ||
isConfirmedByDefault, | ||
}; | ||
} | ||
|
||
function determineRequiresConfirmation(eventType: EventType, bookingStartTime: string): boolean { | ||
let requiresConfirmation = eventType?.requiresConfirmation; | ||
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold; | ||
|
||
if (rcThreshold) { | ||
const timeDifference = dayjs(dayjs(bookingStartTime).utc().format()).diff(dayjs(), rcThreshold.unit); | ||
if (timeDifference > rcThreshold.time) { | ||
requiresConfirmation = false; | ||
} | ||
} | ||
|
||
return requiresConfirmation; | ||
} | ||
|
||
function isUserReschedulingOwner( | ||
userId: number | undefined, | ||
originalRescheduledBookingOrganizerId: number | undefined | ||
): boolean { | ||
// If the user is not the owner of the event, new booking should be always pending. | ||
// Otherwise, an owner rescheduling should be always accepted. | ||
// Before comparing make sure that userId is set, otherwise undefined === undefined | ||
return !!(userId && originalRescheduledBookingOrganizerId === userId); | ||
} | ||
|
||
function determineIsConfirmedByDefault( | ||
requiresConfirmation: boolean, | ||
price: number, | ||
userReschedulingIsOwner: boolean | ||
): boolean { | ||
return (!requiresConfirmation && price === 0) || userReschedulingIsOwner; | ||
} |
60 changes: 60 additions & 0 deletions
60
packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; | ||
import type { EventResult } from "@calcom/types/EventManager"; | ||
|
||
import type { ReqAppsStatus, Booking } from "../handleNewBooking"; | ||
|
||
export function handleAppsStatus( | ||
results: EventResult<AdditionalInformation>[], | ||
booking: (Booking & { appsStatus?: AppsStatus[] }) | null, | ||
reqAppsStatus: ReqAppsStatus | ||
): AppsStatus[] { | ||
const resultStatus = mapResultsToAppsStatus(results); | ||
|
||
if (reqAppsStatus === undefined) { | ||
return updateBookingWithStatus(booking, resultStatus); | ||
} | ||
|
||
return calculateAggregatedAppsStatus(reqAppsStatus, resultStatus); | ||
} | ||
|
||
function mapResultsToAppsStatus(results: EventResult<AdditionalInformation>[]): AppsStatus[] { | ||
return results.map((app) => ({ | ||
appName: app.appName, | ||
type: app.type, | ||
success: app.success ? 1 : 0, | ||
failures: !app.success ? 1 : 0, | ||
errors: app.calError ? [app.calError] : [], | ||
warnings: app.calWarnings, | ||
})); | ||
} | ||
|
||
function updateBookingWithStatus( | ||
booking: (Booking & { appsStatus?: AppsStatus[] }) | null, | ||
resultStatus: AppsStatus[] | ||
): AppsStatus[] { | ||
if (booking !== null) { | ||
booking.appsStatus = resultStatus; | ||
} | ||
return resultStatus; | ||
} | ||
|
||
function calculateAggregatedAppsStatus( | ||
reqAppsStatus: NonNullable<ReqAppsStatus>, | ||
resultStatus: AppsStatus[] | ||
): AppsStatus[] { | ||
// From down here we can assume reqAppsStatus is not undefined anymore | ||
// Other status exist, so this is the last booking of a series, | ||
// proceeding to prepare the info for the event | ||
const aggregatedStatus = reqAppsStatus.concat(resultStatus).reduce((acc, curr) => { | ||
if (acc[curr.type]) { | ||
acc[curr.type].success += curr.success; | ||
acc[curr.type].errors = acc[curr.type].errors.concat(curr.errors); | ||
acc[curr.type].warnings = acc[curr.type].warnings?.concat(curr.warnings || []); | ||
} else { | ||
acc[curr.type] = curr; | ||
} | ||
return acc; | ||
}, {} as { [key: string]: AppsStatus }); | ||
|
||
return Object.values(aggregatedStatus); | ||
} |
56 changes: 56 additions & 0 deletions
56
packages/features/bookings/lib/handleNewBooking/handleCustomInputs.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import type { EventTypeCustomInput } from "@prisma/client"; | ||
import { isValidPhoneNumber } from "libphonenumber-js"; | ||
import z from "zod"; | ||
|
||
type CustomInput = { | ||
value: string | boolean; | ||
label: string; | ||
}; | ||
|
||
export function handleCustomInputs( | ||
eventTypeCustomInputs: EventTypeCustomInput[], | ||
reqCustomInputs: CustomInput[] | ||
) { | ||
eventTypeCustomInputs.forEach((etcInput) => { | ||
if (etcInput.required) { | ||
const input = reqCustomInputs.find((input) => input.label === etcInput.label); | ||
validateInput(etcInput, input?.value); | ||
} | ||
}); | ||
} | ||
|
||
function validateInput(etcInput: EventTypeCustomInput, value: string | boolean | undefined) { | ||
const errorMessage = `Missing ${etcInput.type} customInput: '${etcInput.label}'`; | ||
|
||
if (etcInput.type === "BOOL") { | ||
validateBooleanInput(value, errorMessage); | ||
} else if (etcInput.type === "PHONE") { | ||
validatePhoneInput(value, errorMessage); | ||
} else { | ||
validateStringInput(value, errorMessage); | ||
} | ||
} | ||
|
||
function validateBooleanInput(value: string | boolean | undefined, errorMessage: string) { | ||
z.literal(true, { | ||
errorMap: () => ({ message: errorMessage }), | ||
}).parse(value); | ||
} | ||
|
||
function validatePhoneInput(value: string | boolean | undefined, errorMessage: string) { | ||
z.string({ | ||
errorMap: () => ({ message: errorMessage }), | ||
}) | ||
.refine((val) => isValidPhoneNumber(val), { | ||
message: "Phone number is invalid", | ||
}) | ||
.parse(value); | ||
} | ||
|
||
function validateStringInput(value: string | boolean | undefined, errorMessage: string) { | ||
z.string({ | ||
errorMap: () => ({ message: errorMessage }), | ||
}) | ||
.min(1) | ||
.parse(value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.