diff --git a/apps/api/v1/lib/validations/booking.ts b/apps/api/v1/lib/validations/booking.ts index f09e805423..33b61dacb3 100644 --- a/apps/api/v1/lib/validations/booking.ts +++ b/apps/api/v1/lib/validations/booking.ts @@ -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, diff --git a/apps/api/v1/lib/validations/shared/queryExpandRelations.ts b/apps/api/v1/lib/validations/shared/queryExpandRelations.ts new file mode 100644 index 0000000000..f6a5115deb --- /dev/null +++ b/apps/api/v1/lib/validations/shared/queryExpandRelations.ts @@ -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(); diff --git a/apps/api/v1/pages/api/bookings/[id]/_get.ts b/apps/api/v1/pages/api/bookings/[id]/_get.ts index bc8511b586..01eafee532 100644 --- a/apps/api/v1/pages/api/bookings/[id]/_get.ts +++ b/apps/api/v1/pages/api/bookings/[id]/_get.ts @@ -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) }; } diff --git a/apps/api/v1/pages/api/bookings/_get.ts b/apps/api/v1/pages/api/bookings/_get.ts index 2174841e97..e9ec08b4b7 100644 --- a/apps/api/v1/pages/api/bookings/_get.ts +++ b/apps/api/v1/pages/api/bookings/_get.ts @@ -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); diff --git a/apps/api/v1/test/lib/bookings/_get.integration-test.ts b/apps/api/v1/test/lib/bookings/_get.integration-test.ts index 74206dd83b..4d915a634a 100644 --- a/apps/api/v1/test/lib/bookings/_get.integration-test.ts +++ b/apps/api/v1/test/lib/bookings/_get.integration-test.ts @@ -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({ + 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); + }); + }); + }); });