Compare commits
1 Commits
main
...
gh-readonl
Author | SHA1 | Date | |
---|---|---|---|
|
bfe5b3de43 |
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _BookingModel as Booking, _AttendeeModel, _UserModel } from "@calcom/prisma/zod";
|
import { _BookingModel as Booking, _AttendeeModel, _UserModel, _PaymentModel } from "@calcom/prisma/zod";
|
||||||
import { extendedBookingCreateBody, iso8601 } from "@calcom/prisma/zod-utils";
|
import { extendedBookingCreateBody, iso8601 } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { schemaQueryUserId } from "./shared/queryUserId";
|
import { schemaQueryUserId } from "./shared/queryUserId";
|
||||||
|
@ -49,6 +49,15 @@ export const schemaBookingReadPublic = Booking.extend({
|
||||||
locale: true,
|
locale: true,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
payment: z
|
||||||
|
.array(
|
||||||
|
_PaymentModel.pick({
|
||||||
|
id: true,
|
||||||
|
success: true,
|
||||||
|
paymentOption: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
}).pick({
|
}).pick({
|
||||||
id: true,
|
id: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
@ -61,6 +70,7 @@ export const schemaBookingReadPublic = Booking.extend({
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
attendees: true,
|
attendees: true,
|
||||||
user: true,
|
user: true,
|
||||||
|
payment: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
status: true,
|
status: true,
|
||||||
responses: true,
|
responses: true,
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { withValidation } from "next-validations";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { baseApiParams } from "./baseApiParams";
|
||||||
|
|
||||||
|
// Extracted out as utility function so can be reused
|
||||||
|
// at different endpoints that require this validation.
|
||||||
|
export const schemaQueryAttendeeEmail = baseApiParams.extend({
|
||||||
|
attendeeEmail: z.string().email(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const schemaQuerySingleOrMultipleAttendeeEmails = z.object({
|
||||||
|
attendeeEmail: z.union([z.string().email(), z.array(z.string().email())]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const withValidQueryAttendeeEmail = withValidation({
|
||||||
|
schema: schemaQueryAttendeeEmail,
|
||||||
|
type: "Zod",
|
||||||
|
mode: "query",
|
||||||
|
});
|
|
@ -32,40 +32,65 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* $ref: "#/components/schemas/ArrayOfBookings"
|
* $ref: "#/components/schemas/Booking"
|
||||||
* examples:
|
* examples:
|
||||||
* bookings:
|
* booking:
|
||||||
* value: [
|
* value:
|
||||||
* {
|
* {
|
||||||
* "id": 1,
|
* "booking": {
|
||||||
* "description": "Meeting with John",
|
* "id": 91,
|
||||||
* "eventTypeId": 2,
|
* "userId": 5,
|
||||||
* "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
|
* "description": "",
|
||||||
* "title": "Business Meeting",
|
* "eventTypeId": 7,
|
||||||
* "startTime": "2023-04-20T10:00:00.000Z",
|
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
|
||||||
* "endTime": "2023-04-20T11:00:00.000Z",
|
* "title": "60min between Pro Example and John Doe",
|
||||||
* "timeZone": "Europe/London",
|
* "startTime": "2023-05-25T09:30:00.000Z",
|
||||||
|
* "endTime": "2023-05-25T10:30:00.000Z",
|
||||||
* "attendees": [
|
* "attendees": [
|
||||||
* {
|
* {
|
||||||
* "email": "example@cal.com",
|
* "email": "john.doe@example.com",
|
||||||
* "name": "John Doe",
|
* "name": "John Doe",
|
||||||
* "timeZone": "Europe/London",
|
* "timeZone": "Asia/Kolkata",
|
||||||
* "locale": "en"
|
* "locale": "en"
|
||||||
* }
|
* }
|
||||||
* ]
|
* ],
|
||||||
|
* "user": {
|
||||||
|
* "email": "pro@example.com",
|
||||||
|
* "name": "Pro Example",
|
||||||
|
* "timeZone": "Asia/Kolkata",
|
||||||
|
* "locale": "en"
|
||||||
|
* },
|
||||||
|
* "payment": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "success": true,
|
||||||
|
* "paymentOption": "ON_BOOKING"
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "metadata": {},
|
||||||
|
* "status": "ACCEPTED",
|
||||||
|
* "responses": {
|
||||||
|
* "email": "john.doe@example.com",
|
||||||
|
* "name": "John Doe",
|
||||||
|
* "location": {
|
||||||
|
* "optionValue": "",
|
||||||
|
* "value": "inPerson"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
* }
|
* }
|
||||||
* ]
|
|
||||||
* 401:
|
* 401:
|
||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
* 404:
|
* 404:
|
||||||
* description: Booking was not found
|
* description: Booking was not found
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function getHandler(req: NextApiRequest) {
|
export async function getHandler(req: NextApiRequest) {
|
||||||
const { prisma, query } = req;
|
const { prisma, query } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
const booking = await prisma.booking.findUnique({
|
const booking = await prisma.booking.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { attendees: true, user: true },
|
include: { attendees: true, user: true, payment: true },
|
||||||
});
|
});
|
||||||
return { booking: schemaBookingReadPublic.parse(booking) };
|
return { booking: schemaBookingReadPublic.parse(booking) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
import { schemaBookingReadPublic } from "~/lib/validations/booking";
|
import { schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||||
|
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +32,19 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||||
* items:
|
* items:
|
||||||
* type: integer
|
* type: integer
|
||||||
* example: [2, 3, 4]
|
* example: [2, 3, 4]
|
||||||
|
* - in: query
|
||||||
|
* name: attendeeEmails
|
||||||
|
* required: false
|
||||||
|
* schema:
|
||||||
|
* oneOf:
|
||||||
|
* - type: string
|
||||||
|
* format: email
|
||||||
|
* example: john.doe@example.com
|
||||||
|
* - type: array
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* format: email
|
||||||
|
* example: [john.doe@example.com, jane.doe@example.com]
|
||||||
* operationId: listBookings
|
* operationId: listBookings
|
||||||
* tags:
|
* tags:
|
||||||
* - bookings
|
* - bookings
|
||||||
|
@ -45,22 +59,47 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||||
* bookings:
|
* bookings:
|
||||||
* value: [
|
* value: [
|
||||||
* {
|
* {
|
||||||
* "id": 1,
|
* "booking": {
|
||||||
* "description": "Meeting with John",
|
* "id": 91,
|
||||||
* "eventTypeId": 2,
|
* "userId": 5,
|
||||||
* "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
|
* "description": "",
|
||||||
* "title": "Business Meeting",
|
* "eventTypeId": 7,
|
||||||
* "startTime": "2023-04-20T10:00:00.000Z",
|
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
|
||||||
* "endTime": "2023-04-20T11:00:00.000Z",
|
* "title": "60min between Pro Example and John Doe",
|
||||||
* "timeZone": "Europe/London",
|
* "startTime": "2023-05-25T09:30:00.000Z",
|
||||||
|
* "endTime": "2023-05-25T10:30:00.000Z",
|
||||||
* "attendees": [
|
* "attendees": [
|
||||||
* {
|
* {
|
||||||
* "email": "example@cal.com",
|
* "email": "john.doe@example.com",
|
||||||
* "name": "John Doe",
|
* "name": "John Doe",
|
||||||
* "timeZone": "Europe/London",
|
* "timeZone": "Asia/Kolkata",
|
||||||
* "locale": "en"
|
* "locale": "en"
|
||||||
* }
|
* }
|
||||||
* ]
|
* ],
|
||||||
|
* "user": {
|
||||||
|
* "email": "pro@example.com",
|
||||||
|
* "name": "Pro Example",
|
||||||
|
* "timeZone": "Asia/Kolkata",
|
||||||
|
* "locale": "en"
|
||||||
|
* },
|
||||||
|
* "payment": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "success": true,
|
||||||
|
* "paymentOption": "ON_BOOKING"
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "metadata": {},
|
||||||
|
* "status": "ACCEPTED",
|
||||||
|
* "responses": {
|
||||||
|
* "email": "john.doe@example.com",
|
||||||
|
* "name": "John Doe",
|
||||||
|
* "location": {
|
||||||
|
* "optionValue": "",
|
||||||
|
* "value": "inPerson"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
* }
|
* }
|
||||||
* ]
|
* ]
|
||||||
* 401:
|
* 401:
|
||||||
|
@ -69,26 +108,42 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||||
* description: No bookings were found
|
* description: No bookings were found
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
/**
|
||||||
const { userId, isAdmin, prisma } = req;
|
* Constructs the WHERE clause for Prisma booking findMany operation.
|
||||||
const args: Prisma.BookingFindManyArgs = {};
|
*
|
||||||
args.include = {
|
* @param userId - The ID of the user making the request. This is used to filter bookings where the user is either the host or an attendee.
|
||||||
attendees: true,
|
* @param attendeeEmails - An array of emails provided in the request for filtering bookings by attendee emails, used in case of Admin calls.
|
||||||
user: true,
|
* @param userIds - An array of user IDs to be included in the filter. Defaults to an empty array, and an array of user IDs in case of Admin call containing it.
|
||||||
|
* @param userEmails - An array of user emails to be included in the filter if it is an Admin call and contains userId in query parameter. Defaults to an empty array.
|
||||||
|
*
|
||||||
|
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
||||||
|
*/
|
||||||
|
function buildWhereClause(
|
||||||
|
userId: number,
|
||||||
|
attendeeEmails: string[],
|
||||||
|
userIds: number[] = [],
|
||||||
|
userEmails: string[] = []
|
||||||
|
) {
|
||||||
|
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||||
|
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : { userId };
|
||||||
|
let whereClause = {};
|
||||||
|
if (filterByAttendeeEmails) {
|
||||||
|
whereClause = {
|
||||||
|
AND: [
|
||||||
|
userFilter,
|
||||||
|
{
|
||||||
|
attendees: {
|
||||||
|
some: {
|
||||||
|
email: { in: attendeeEmails },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
/** Only admins can query other users */
|
whereClause = {
|
||||||
if (isAdmin && req.query.userId) {
|
|
||||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
|
||||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
|
||||||
const users = await prisma.user.findMany({
|
|
||||||
where: { id: { in: userIds } },
|
|
||||||
select: { email: true },
|
|
||||||
});
|
|
||||||
const userEmails = users.map((u) => u.email);
|
|
||||||
args.where = {
|
|
||||||
OR: [
|
OR: [
|
||||||
{ userId: { in: userIds } },
|
userFilter,
|
||||||
{
|
{
|
||||||
attendees: {
|
attendees: {
|
||||||
some: {
|
some: {
|
||||||
|
@ -98,7 +153,45 @@ async function handler(req: NextApiRequest) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} else if (!isAdmin) {
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...whereClause,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handler(req: NextApiRequest) {
|
||||||
|
const { userId, isAdmin, prisma } = req;
|
||||||
|
const args: Prisma.BookingFindManyArgs = {};
|
||||||
|
args.include = {
|
||||||
|
attendees: true,
|
||||||
|
user: true,
|
||||||
|
payment: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryFilterForAttendeeEmails = schemaQuerySingleOrMultipleAttendeeEmails.parse(req.query);
|
||||||
|
const attendeeEmails = Array.isArray(queryFilterForAttendeeEmails.attendeeEmail)
|
||||||
|
? queryFilterForAttendeeEmails.attendeeEmail
|
||||||
|
: typeof queryFilterForAttendeeEmails.attendeeEmail === "string"
|
||||||
|
? [queryFilterForAttendeeEmails.attendeeEmail]
|
||||||
|
: [];
|
||||||
|
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||||
|
|
||||||
|
/** Only admins can query other users */
|
||||||
|
if (isAdmin) {
|
||||||
|
if (req.query.userId) {
|
||||||
|
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||||
|
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: { id: { in: userIds } },
|
||||||
|
select: { email: true },
|
||||||
|
});
|
||||||
|
const userEmails = users.map((u) => u.email);
|
||||||
|
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
||||||
|
} else if (filterByAttendeeEmails) {
|
||||||
|
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
select: {
|
select: {
|
||||||
|
@ -108,20 +201,7 @@ async function handler(req: NextApiRequest) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpError({ message: "User not found", statusCode: 500 });
|
throw new HttpError({ message: "User not found", statusCode: 500 });
|
||||||
}
|
}
|
||||||
args.where = {
|
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
attendees: {
|
|
||||||
some: {
|
|
||||||
email: user.email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const data = await prisma.booking.findMany(args);
|
const data = await prisma.booking.findMany(args);
|
||||||
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
||||||
|
|
|
@ -98,6 +98,25 @@ const swaggerHandler = withSwagger({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
payment: {
|
||||||
|
type: Array,
|
||||||
|
items: {
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: "number",
|
||||||
|
example: 1,
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: "boolean",
|
||||||
|
example: true,
|
||||||
|
},
|
||||||
|
paymentOption: {
|
||||||
|
type: "string",
|
||||||
|
example: "ON_BOOKING",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user