Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 141 additions & 2 deletions packages/features/bookings/lib/dto/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,32 @@
*/
import type getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema";
import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
import type { BookingRepository } from "@calcom/lib/server/repository/booking";
import type { SchedulingType } from "@calcom/prisma/enums";
import type { BookingCreateBody as BaseCreateBookingData } from "@calcom/prisma/zod/custom/booking";

import type { ExtendedBookingCreateBody } from "../bookingCreateBodySchema";
import type { extendedBookingCreateBody } from "../bookingCreateBodySchema";
import type { Booking } from "../handleNewBooking/createBooking";
import type { InstantBookingCreateService } from "../service/InstantBookingCreateService";
import type { RegularBookingService } from "../service/RegularBookingService";

export type { BookingCreateBody } from "../bookingCreateBodySchema";

// Use ReturnType from booking repository for type safety
type ExistingBooking = Awaited<ReturnType<BookingRepository["getValidBookingFromEventTypeForAttendee"]>>;

interface ExistingBookingResponse extends Omit<NonNullable<ExistingBooking>, "user"> {
user: Omit<NonNullable<ExistingBooking>["user"], "email"> & { email: null };
paymentRequired: boolean;
seatReferenceUid: string;
luckyUsers: number[];
isDryRun: boolean;
troubleshooterData?: Record<string, unknown>;
paymentUid?: string;
paymentId?: number;
}
export type ExtendedBookingCreateData = z.input<typeof extendedBookingCreateBody>;
export type BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi;

export type CreateRegularBookingData = ExtendedBookingCreateBody;
Expand All @@ -21,7 +40,7 @@ export type CreateRecurringBookingData = (ExtendedBookingCreateBody & {
schedulingType?: SchedulingType;
})[];

export type CreateSeatedBookingInput = BaseCreateBookingData & Pick<MasterCreateBookingData, "bookingUid">;
export type CreateSeatedBookingInput = BaseCreateBookingData & Pick<BaseCreateBookingData, "bookingUid">;

export type PlatformParams = {
platformClientId?: string;
Expand All @@ -42,9 +61,129 @@ export type CreateBookingMeta = {

export type BookingHandlerInput = {
bookingData: CreateRegularBookingData;
} & CreateBookingMeta;
bookingMeta: CreateBookingMeta;
};

// TODO: In a followup PR, we working on defining the type here itself instead of inferring it.
export type RegularBookingCreateResult = Awaited<ReturnType<RegularBookingService["createBooking"]>>;

export type CreateInstantBookingResponse = {
message: string;
meetingTokenId: number;
bookingId: number;
bookingUid: string;
expires: Date;
userId: number | null;
};

// TODO: Ideally we should define the types here instead of letting BookingCreateService send anything and keep using it
export type BookingCreateResult = Awaited<ReturnType<BookingCreateService["create"]>>;

export type InstantBookingCreateResult = Awaited<ReturnType<InstantBookingCreateService["create"]>>;

// Type for booking with additional fields from the creation process
export type CreatedBooking = Booking & {
appsStatus?: import("@calcom/types/Calendar").AppsStatus[];
paymentUid?: string;
paymentId?: number;
};

// Base user type that ensures timeZone and name are always available
type BookingUser = {
id?: number;
name?: string | null;
username?: string | null;
email?: null;
timeZone?: string;
} | null;

// Discriminated union for legacyHandler return types
export type LegacyHandlerResult =
// Early return case - existing booking found
| (Omit<ExistingBookingResponse, "user"> & {
_type: "existing";
user: BookingUser;
paymentUid?: string;
})
// Payment required case
| {
_type: "payment_required";
id?: number;
uid?: string;
title?: string;
description?: string | null;
startTime?: Date;
endTime?: Date;
location?: string | null;
status?: import("@calcom/prisma/enums").BookingStatus;
metadata?: import("@prisma/client").Prisma.JsonValue | null;
user?: BookingUser;
attendees?: Array<{
id: number;
name: string;
email: string;
timeZone: string;
phoneNumber: string | null;
}>;
eventType?: {
id?: number;
title?: string;
slug?: string;
} | null;
paymentRequired: true;
message: string;
paymentUid?: string;
paymentId?: number;
isDryRun?: boolean;
troubleshooterData?: Record<string, unknown>;
luckyUsers?: number[];
userPrimaryEmail?: string | null;
responses?: import("@prisma/client").Prisma.JsonValue | null;
references?: PartialReference[] | CreatedBooking["references"];
seatReferenceUid?: string;
videoCallUrl?: string | null;
}
// Successful booking case
| {
_type: "success";
id?: number;
uid?: string;
title?: string;
description?: string | null;
startTime?: Date;
endTime?: Date;
location?: string | null;
status?: import("@calcom/prisma/enums").BookingStatus;
metadata?: import("@prisma/client").Prisma.JsonValue | null;
user?: BookingUser;
attendees?: Array<{
id: number;
name: string;
email: string;
timeZone: string;
phoneNumber: string | null;
}>;
eventType?: {
id?: number;
title?: string;
slug?: string;
} | null;
paymentRequired: false;
isDryRun?: boolean;
troubleshooterData?: Record<string, unknown>;
luckyUsers?: number[];
paymentUid?: string;
userPrimaryEmail?: string | null;
responses?: import("@prisma/client").Prisma.JsonValue | null;
references?: PartialReference[] | CreatedBooking["references"];
seatReferenceUid?: string;
videoCallUrl?: string | null;
};

export type BookingFlowConfig = {
isDryRun: boolean;
useCacheIfEnabled: boolean;
noEmail: boolean;
hostname: string | null;
forcedSlug: string | null;
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { ErrorCode } from "@calcom/lib/errorCodes";
import { ErrorWithCode } from "@calcom/lib/errors";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import type { BookingRepository } from "@calcom/lib/server/repository/booking";

const log = logger.getSubLogger({ prefix: ["[checkActiveBookingsLimitForBooker]"] });

Expand All @@ -11,11 +10,13 @@ export const checkActiveBookingsLimitForBooker = async ({
maxActiveBookingsPerBooker,
bookerEmail,
offerToRescheduleLastBooking,
bookingRepository,
}: {
eventTypeId: number;
maxActiveBookingsPerBooker: number | null;
bookerEmail: string;
offerToRescheduleLastBooking: boolean;
bookingRepository: BookingRepository;
}) => {
if (!maxActiveBookingsPerBooker) {
return;
Expand All @@ -26,9 +27,15 @@ export const checkActiveBookingsLimitForBooker = async ({
eventTypeId,
maxActiveBookingsPerBooker,
bookerEmail,
bookingRepository,
});
} else {
await checkActiveBookingsLimit({ eventTypeId, maxActiveBookingsPerBooker, bookerEmail });
await checkActiveBookingsLimit({
eventTypeId,
maxActiveBookingsPerBooker,
bookerEmail,
bookingRepository,
});
}
};

Expand All @@ -37,26 +44,16 @@ const checkActiveBookingsLimit = async ({
eventTypeId,
maxActiveBookingsPerBooker,
bookerEmail,
bookingRepository,
}: {
eventTypeId: number;
maxActiveBookingsPerBooker: number;
bookerEmail: string;
bookingRepository: BookingRepository;
}) => {
const bookingsCount = await prisma.booking.count({
where: {
eventTypeId,
startTime: {
gte: new Date(),
},
status: {
in: [BookingStatus.ACCEPTED],
},
attendees: {
some: {
email: bookerEmail,
},
},
},
const bookingsCount = await bookingRepository.countActiveBookingsForEventType({
eventTypeId,
bookerEmail,
});

if (bookingsCount >= maxActiveBookingsPerBooker) {
Expand All @@ -69,40 +66,17 @@ const checkActiveBookingsLimitAndOfferReschedule = async ({
eventTypeId,
maxActiveBookingsPerBooker,
bookerEmail,
bookingRepository,
}: {
eventTypeId: number;
maxActiveBookingsPerBooker: number;
bookerEmail: string;
bookingRepository: BookingRepository;
}) => {
const bookingsCount = await prisma.booking.findMany({
where: {
eventTypeId,
startTime: {
gte: new Date(),
},
status: {
in: [BookingStatus.ACCEPTED],
},
attendees: {
some: {
email: bookerEmail,
},
},
},
orderBy: {
startTime: "desc",
},
take: maxActiveBookingsPerBooker,
select: {
uid: true,
startTime: true,
attendees: {
select: {
name: true,
email: true,
},
},
},
const bookingsCount = await bookingRepository.findActiveBookingsForEventType({
eventTypeId,
bookerEmail,
limit: maxActiveBookingsPerBooker,
});

const lastBooking = bookingsCount[bookingsCount.length - 1];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { extractBaseEmail } from "@calcom/lib/extract-base-email";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import type { UserRepository } from "@calcom/lib/server/repository/user";

export const checkIfBookerEmailIsBlocked = async ({
bookerEmail,
loggedInUserId,
userRepository,
}: {
bookerEmail: string;
loggedInUserId?: number;
userRepository: UserRepository;
}) => {
const baseEmail = extractBaseEmail(bookerEmail);
const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS
Expand All @@ -17,37 +19,11 @@ export const checkIfBookerEmailIsBlocked = async ({
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,
},
});
const user = await userRepository.findVerifiedUserByEmail({ email: baseEmail });

if (!user) {
throw new HttpError({ statusCode: 403, message: "Cannot use this email to create the booking." });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// eslint-disable-next-line no-restricted-imports
import type { BookingHandlerInput } from "@calcom/features/bookings/lib/dto/types";

async function handler(input: BookingHandlerInput) {
async function handler(
input: {
bookingData: BookingHandlerInput["bookingData"];
} & BookingHandlerInput["bookingMeta"]
) {
const { getRegularBookingService } = await import(
"@calcom/lib/di/bookings/containers/RegularBookingService.container"
);
Expand Down
Loading
Loading