Adds eventTypeId as a parameter (#1217)

This commit is contained in:
Alex van Andel 2021-12-03 17:18:31 +01:00 committed by GitHub
parent 8c1b69cc0f
commit 22aa083883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 59 deletions

View File

@ -19,6 +19,7 @@ import { createPaymentLink } from "@ee/lib/stripe/client";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme";
import { LocationType } from "@lib/location";
@ -137,19 +138,12 @@ const BookingPage = (props: BookingPageProps) => {
};
};
// can be shortened using .filter(), except TypeScript doesn't know what to make of the types.
const guests = router.query.guest
? Array.isArray(router.query.guest)
? router.query.guest
: [router.query.guest]
: [];
const bookingForm = useForm<BookingFormValues>({
defaultValues: {
name: (router.query.name as string) || "",
email: (router.query.email as string) || "",
notes: (router.query.notes as string) || "",
guests,
guests: ensureArray(router.query.guest),
customInputs: props.eventType.customInputs.reduce(
(customInputs, input) => ({
...customInputs,
@ -213,7 +207,7 @@ const BookingPage = (props: BookingPageProps) => {
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
user: router.query.user as string,
user: router.query.user,
location: getLocationValue(booking),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({

View File

@ -14,6 +14,7 @@ const config: Config.InitialOptions = {
"^@components(.*)$": "<rootDir>/components$1",
"^@lib(.*)$": "<rootDir>/lib$1",
"^@server(.*)$": "<rootDir>/server$1",
"^@ee(.*)$": "<rootDir>/ee$1",
},
};

9
lib/ensureArray.ts Normal file
View File

@ -0,0 +1,9 @@
export function ensureArray<T>(val: unknown): T[] {
if (Array.isArray(val)) {
return val;
}
if (typeof val === "undefined") {
return [];
}
return [val] as T[];
}

View File

@ -16,8 +16,7 @@ export type BookingCreateBody = {
rescheduleUid?: string;
start: string;
timeZone: string;
users?: string[];
user?: string;
user?: string | string[];
language: string;
customInputs: { label: string; value: string }[];
metadata: {

View File

@ -17,6 +17,7 @@ import {
sendRescheduledEmails,
sendOrganizerRequestEmail,
} from "@lib/emails/email-manager";
import { ensureArray } from "@lib/ensureArray";
import { getErrorFromUnknown } from "@lib/errors";
import { getEventName } from "@lib/event";
import EventManager, { EventResult, PartialReference } from "@lib/events/EventManager";
@ -123,6 +124,59 @@ function isOutOfBounds(
}
}
const userSelect = Prisma.validator<Prisma.UserArgs>()({
select: {
id: true,
email: true,
name: true,
username: true,
timeZone: true,
credentials: true,
bufferTime: true,
},
});
const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNames: string[]) => {
const users = await prisma.user.findMany({
where: {
username: { in: selectedUserNames },
bookings: {
some: {
startTime: {
gt: new Date(),
},
eventTypeId,
},
},
},
select: {
id: true,
username: true,
},
});
const userNamesWithBookingCounts = await Promise.all(
users.map(async (user) => ({
username: user.username,
bookingCount: await prisma.booking.count({
where: {
user: {
id: user.id,
},
startTime: {
gt: new Date(),
},
eventTypeId,
},
}),
}))
);
return userNamesWithBookingCounts;
};
type User = Prisma.UserGetPayload<typeof userSelect>;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const reqBody = req.body as BookingCreateBody;
const eventTypeId = reqBody.eventTypeId;
@ -144,28 +198,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json(error);
}
const userSelect = Prisma.validator<Prisma.UserSelect>()({
id: true,
email: true,
name: true,
username: true,
timeZone: true,
credentials: true,
bufferTime: true,
});
const userData = Prisma.validator<Prisma.UserArgs>()({
select: userSelect,
});
const eventType = await prisma.eventType.findUnique({
where: {
id: eventTypeId,
},
select: {
users: {
select: userSelect,
},
users: userSelect,
team: {
select: {
id: true,
@ -198,43 +236,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
where: {
id: eventType.userId,
},
select: userSelect,
...userSelect,
});
if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" });
users.push(eventTypeUser);
}
if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
const selectedUsers = reqBody.users || [];
const selectedUsersDataWithBookingsCount = await prisma.user.findMany({
where: {
username: { in: selectedUsers },
bookings: {
every: {
startTime: {
gt: new Date(),
},
},
},
},
select: {
username: true,
_count: {
select: { bookings: true },
},
},
});
const bookingCounts = await getUserNameWithBookingCounts(
eventTypeId,
ensureArray(reqBody.user) || users.map((user) => user.username)
);
const bookingCounts = selectedUsersDataWithBookingsCount.map((userData) => ({
username: userData.username,
bookingCount: userData._count?.bookings || 0,
}));
if (!bookingCounts.length) users.slice(0, 1);
const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1));
const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username);
users = luckyUser ? [luckyUser] : users;
users = getLuckyUsers(users, bookingCounts);
}
const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }];
@ -354,7 +368,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
let results: EventResult[] = [];
let referencesToCreate: PartialReference[] = [];
type User = Prisma.UserGetPayload<typeof userData>;
let user: User | null = null;
for (const currentUser of users) {
@ -555,3 +568,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// booking successful
return res.status(201).json(booking);
}
export function getLuckyUsers(
users: User[],
bookingCounts: Prisma.PromiseReturnType<typeof getUserNameWithBookingCounts>
) {
if (!bookingCounts.length) users.slice(0, 1);
const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1));
const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username);
return luckyUser ? [luckyUser] : users;
}

View File

@ -0,0 +1,30 @@
import { getLuckyUsers } from "../../pages/api/book/event";
it("can find lucky users", async () => {
const users = [
{
id: 1,
username: "test",
name: "Test User",
credentials: [],
timeZone: "GMT",
bufferTime: 0,
email: "test@example.com",
},
{
id: 2,
username: "test2",
name: "Test 2 User",
credentials: [],
timeZone: "GMT",
bufferTime: 0,
email: "test2@example.com",
},
];
expect(
getLuckyUsers(users, [
{ username: "test", bookingCount: 2 },
{ username: "test2", bookingCount: 0 },
])
).toStrictEqual([users[1]]);
});