Skip to content

Commit

Permalink
chore: refactor handle new reccuring booking (#13597)
Browse files Browse the repository at this point in the history
* chore: refactor handle new reccuring booking

* fixup! chore: refactor handle new reccuring booking

* fixup! fixup! chore: refactor handle new reccuring booking
  • Loading branch information
ThyMinimalDev committed Feb 9, 2024
1 parent a5ebd33 commit 465824f
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 89 deletions.
92 changes: 3 additions & 89 deletions apps/web/pages/api/book/recurring-event.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { NextApiRequest, NextApiResponse } from "next";

import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
import type { BookingResponse, RecurringBookingCreateBody } from "@calcom/features/bookings/types";
import { handleNewRecurringBooking } from "@calcom/features/bookings/lib/handleNewRecurringBooking";
import type { BookingResponse } from "@calcom/features/bookings/types";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import getIP from "@calcom/lib/getIP";
import { defaultResponder } from "@calcom/lib/server";
import { SchedulingType } from "@calcom/prisma/client";
import type { AppsStatus } from "@calcom/types/Calendar";

// @TODO: Didn't look at the contents of this function in order to not break old booking page.

Expand All @@ -18,96 +16,12 @@ async function handler(req: NextApiRequest & { userId?: number }, res: NextApiRe
rateLimitingType: "core",
identifier: userIp,
});
const data: RecurringBookingCreateBody[] = req.body;
const session = await getServerSession({ req, res });
const createdBookings: BookingResponse[] = [];
const allRecurringDates: { start: string | undefined; end: string | undefined }[] = data.map((booking) => {
return { start: booking.start, end: booking.end };
});
const appsStatus: AppsStatus[] | undefined = undefined;

/* To mimic API behavior and comply with types */
req.userId = session?.user?.id || -1;
const numSlotsToCheckForAvailability = 2;

let thirdPartyRecurringEventId = null;

// for round robin, the first slot needs to be handled first to define the lucky user
const firstBooking = data[0];
const isRoundRobin = firstBooking.schedulingType === SchedulingType.ROUND_ROBIN;

let luckyUsers = undefined;

if (isRoundRobin) {
const recurringEventReq: NextApiRequest & { userId?: number } = req;

recurringEventReq.body = {
...firstBooking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: true,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: 0,
noEmail: false,
};

const firstBookingResult = await handleNewBooking(recurringEventReq);
luckyUsers = firstBookingResult.luckyUsers?.map((user) => user.id);
}

for (let key = isRoundRobin ? 1 : 0; key < data.length; key++) {
const booking = data[key];
// Disable AppStatus in Recurring Booking Email as it requires us to iterate backwards to be able to compute the AppsStatus for all the bookings except the very first slot and then send that slot's email with statuses
// It is also doubtful that how useful is to have the AppsStatus of all the bookings in the email.
// It is more important to iterate forward and check for conflicts for only first few bookings defined by 'numSlotsToCheckForAvailability'
// if (key === 0) {
// const calcAppsStatus: { [key: string]: AppsStatus } = createdBookings
// .flatMap((book) => (book.appsStatus !== undefined ? book.appsStatus : []))
// .reduce((prev, curr) => {
// if (prev[curr.type]) {
// prev[curr.type].failures += curr.failures;
// prev[curr.type].success += curr.success;
// } else {
// prev[curr.type] = curr;
// }
// return prev;
// }, {} as { [key: string]: AppsStatus });
// appsStatus = Object.values(calcAppsStatus);
// }

const recurringEventReq: NextApiRequest & { userId?: number } = req;

recurringEventReq.body = {
...booking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: key == 0,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: key,
noEmail: key !== 0,
luckyUsers,
};

const promiseEachRecurringBooking: ReturnType<typeof handleNewBooking> =
handleNewBooking(recurringEventReq);

const eachRecurringBooking = await promiseEachRecurringBooking;

createdBookings.push(eachRecurringBooking);
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(req);

if (!thirdPartyRecurringEventId) {
if (eachRecurringBooking.references && eachRecurringBooking.references.length > 0) {
for (const reference of eachRecurringBooking.references!) {
if (reference.thirdPartyRecurringEventId) {
thirdPartyRecurringEventId = reference.thirdPartyRecurringEventId;
break;
}
}
}
}
}
return createdBookings;
}

Expand Down
99 changes: 99 additions & 0 deletions packages/features/bookings/lib/handleNewRecurringBooking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { NextApiRequest } from "next";

import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
import type { RecurringBookingCreateBody, BookingResponse } from "@calcom/features/bookings/types";
import { SchedulingType } from "@calcom/prisma/client";
import type { AppsStatus } from "@calcom/types/Calendar";

export const handleNewRecurringBooking = async (
req: NextApiRequest & { userId?: number }
): Promise<BookingResponse[]> => {
const data: RecurringBookingCreateBody[] = req.body;
const createdBookings: BookingResponse[] = [];
const allRecurringDates: { start: string | undefined; end: string | undefined }[] = data.map((booking) => {
return { start: booking.start, end: booking.end };
});
const appsStatus: AppsStatus[] | undefined = undefined;

const numSlotsToCheckForAvailability = 2;

let thirdPartyRecurringEventId = null;

// for round robin, the first slot needs to be handled first to define the lucky user
const firstBooking = data[0];
const isRoundRobin = firstBooking.schedulingType === SchedulingType.ROUND_ROBIN;

let luckyUsers = undefined;

if (isRoundRobin) {
const recurringEventReq: NextApiRequest & { userId?: number } = req;

recurringEventReq.body = {
...firstBooking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: true,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: 0,
noEmail: false,
};

const firstBookingResult = await handleNewBooking(recurringEventReq);
luckyUsers = firstBookingResult.luckyUsers?.map((user) => user.id);
}

for (let key = isRoundRobin ? 1 : 0; key < data.length; key++) {
const booking = data[key];
// Disable AppStatus in Recurring Booking Email as it requires us to iterate backwards to be able to compute the AppsStatus for all the bookings except the very first slot and then send that slot's email with statuses
// It is also doubtful that how useful is to have the AppsStatus of all the bookings in the email.
// It is more important to iterate forward and check for conflicts for only first few bookings defined by 'numSlotsToCheckForAvailability'
// if (key === 0) {
// const calcAppsStatus: { [key: string]: AppsStatus } = createdBookings
// .flatMap((book) => (book.appsStatus !== undefined ? book.appsStatus : []))
// .reduce((prev, curr) => {
// if (prev[curr.type]) {
// prev[curr.type].failures += curr.failures;
// prev[curr.type].success += curr.success;
// } else {
// prev[curr.type] = curr;
// }
// return prev;
// }, {} as { [key: string]: AppsStatus });
// appsStatus = Object.values(calcAppsStatus);
// }

const recurringEventReq: NextApiRequest & { userId?: number } = req;

recurringEventReq.body = {
...booking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: key == 0,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: key,
noEmail: key !== 0,
luckyUsers,
};

const promiseEachRecurringBooking: ReturnType<typeof handleNewBooking> =
handleNewBooking(recurringEventReq);

const eachRecurringBooking = await promiseEachRecurringBooking;

createdBookings.push(eachRecurringBooking);

if (!thirdPartyRecurringEventId) {
if (eachRecurringBooking.references && eachRecurringBooking.references.length > 0) {
for (const reference of eachRecurringBooking.references!) {
if (reference.thirdPartyRecurringEventId) {
thirdPartyRecurringEventId = reference.thirdPartyRecurringEventId;
break;
}
}
}
}
}
return createdBookings;
};

0 comments on commit 465824f

Please sign in to comment.