feat: Add EXPAND accepting "team" in API V1 Bookings (#16736)

* add expand

* add expand to /[id]/_get

* add test

* fix test import

* --

* --

* fix test

* fix test

* revert unintentional commit
This commit is contained in:
Syed Ali Shahbaz
2024-09-20 11:46:26 +00:00
committed by GitHub
parent 11f0d48d0b
commit 8cb5a7080b
5 changed files with 83 additions and 3 deletions
+25 -1
View File
@@ -1,6 +1,13 @@
import { z } from "zod";
import { _AttendeeModel, _BookingModel as Booking, _PaymentModel, _UserModel } from "@calcom/prisma/zod";
import {
_AttendeeModel,
_BookingModel as Booking,
_EventTypeModel,
_PaymentModel,
_TeamModel,
_UserModel,
} from "@calcom/prisma/zod";
import { extendedBookingCreateBody, iso8601 } from "@calcom/prisma/zod-utils";
import { schemaQueryUserId } from "./shared/queryUserId";
@@ -46,7 +53,23 @@ export const schemaBookingEditBodyParams = schemaBookingBaseBodyParams
.merge(schemaBookingEditParams)
.omit({ uid: true });
const teamSchema = _TeamModel.pick({
name: true,
slug: true,
});
export const schemaBookingReadPublic = Booking.extend({
eventType: _EventTypeModel
.pick({
title: true,
slug: true,
})
.merge(
z.object({
team: teamSchema.nullish(),
})
)
.nullish(),
attendees: z
.array(
_AttendeeModel.pick({
@@ -87,6 +110,7 @@ export const schemaBookingReadPublic = Booking.extend({
timeZone: true,
attendees: true,
user: true,
eventType: true,
payment: true,
metadata: true,
status: true,
@@ -0,0 +1,12 @@
import { z } from "zod";
const expandEnum = z.enum(["team"]);
export const schemaQuerySingleOrMultipleExpand = z
.union([
expandEnum, // Allow a single value from the enum
z.array(expandEnum).refine((arr) => new Set(arr).size === arr.length, {
message: "Array values must be unique",
}), // Allow an array of enum values, with uniqueness constraint
])
.optional();
+14 -1
View File
@@ -4,6 +4,7 @@ import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { schemaBookingReadPublic } from "~/lib/validations/booking";
import { schemaQuerySingleOrMultipleExpand } from "~/lib/validations/shared/queryExpandRelations";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
/**
@@ -89,9 +90,21 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
export async function getHandler(req: NextApiRequest) {
const { query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const queryFilterForExpand = schemaQuerySingleOrMultipleExpand.parse(req.query.expand);
const expand = Array.isArray(queryFilterForExpand)
? queryFilterForExpand
: queryFilterForExpand
? [queryFilterForExpand]
: [];
const booking = await prisma.booking.findUnique({
where: { id },
include: { attendees: true, user: true, payment: true },
include: {
attendees: true,
user: true,
payment: true,
eventType: expand.includes("team") ? { include: { team: true } } : false,
},
});
return { booking: schemaBookingReadPublic.parse(booking) };
}
+9
View File
@@ -12,6 +12,7 @@ import {
} from "~/lib/utils/retrieveScopedAccessibleUsers";
import { schemaBookingGetParams, schemaBookingReadPublic } from "~/lib/validations/booking";
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
import { schemaQuerySingleOrMultipleExpand } from "~/lib/validations/shared/queryExpandRelations";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
/**
@@ -216,10 +217,18 @@ export async function handler(req: NextApiRequest) {
args.take = take;
args.skip = skip;
}
const queryFilterForExpand = schemaQuerySingleOrMultipleExpand.parse(req.query.expand);
const expand = Array.isArray(queryFilterForExpand)
? queryFilterForExpand
: queryFilterForExpand
? [queryFilterForExpand]
: [];
args.include = {
attendees: true,
user: true,
payment: true,
eventType: expand.includes("team") ? { include: { team: true } } : false,
};
const queryFilterForAttendeeEmails = schemaQuerySingleOrMultipleAttendeeEmails.parse(req.query);
@@ -164,7 +164,6 @@ describe("GET /api/bookings", async () => {
const responseData = await handler(req);
responseData.bookings.forEach((booking) => {
console.log(booking);
expect(new Date(booking.startTime).getTime()).toBeGreaterThanOrEqual(new Date().getTime());
});
});
@@ -188,4 +187,27 @@ describe("GET /api/bookings", async () => {
});
});
});
describe("Expand feature to add relational data in return payload", () => {
it("Returns only team data when expand=team is set", async () => {
const adminUser = await prisma.user.findFirstOrThrow({ where: { email: "owner1-acme@example.com" } });
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
query: {
expand: "team",
},
pagination: DefaultPagination,
});
req.userId = adminUser.id;
req.isOrganizationOwnerOrAdmin = true;
const responseData = await handler(req);
console.log("bookings=>", responseData.bookings);
responseData.bookings.forEach((booking) => {
if (booking.id === 31) expect(booking.eventType?.team?.slug).toBe("team1");
if (booking.id === 19) expect(booking.eventType?.team).toBe(null);
});
});
});
});