diff --git a/pages/api/attendees/[id].ts b/pages/api/attendees/[id].ts deleted file mode 100644 index d7e927e36e..0000000000 --- a/pages/api/attendees/[id].ts +++ /dev/null @@ -1,180 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -import { withMiddleware } from "@lib/helpers/withMiddleware"; -import type { AttendeeResponse } from "@lib/types"; -import { schemaAttendeeEditBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee"; -import { - schemaQueryIdParseInt, - withValidQueryIdTransformParseInt, -} from "@lib/validations/shared/queryIdTransformParseInt"; - -export async function attendeeById( - { method, query, body, userId, isAdmin, prisma }: NextApiRequest, - res: NextApiResponse -) { - const safeQuery = schemaQueryIdParseInt.safeParse(query); - if (!safeQuery.success) { - res.status(400).json({ error: safeQuery.error }); - return; - } - const userBookingsAttendeeIds = await prisma.booking - // Find all user bookings, including attendees - .findMany({ - where: { userId }, - include: { attendees: true }, - }) - .then( - // Flatten and merge all the attendees in one array - (bookings) => - bookings - .map((bookings) => bookings.attendees) - .flat() - .map((attendee) => attendee.id) - ); - // @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin. - if (!isAdmin) { - if (!userBookingsAttendeeIds.includes(safeQuery.data.id)) - res.status(401).json({ message: "Unauthorized" }); - } else { - switch (method) { - /** - * @swagger - * /attendees/{id}: - * get: - * operationId: getAttendeeById - * summary: Find an attendee - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the attendee to get - * example: 3 - * tags: - * - attendees - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: Attendee was not found - */ - case "GET": - await prisma.attendee - .findUnique({ where: { id: safeQuery.data.id } }) - .then((data) => schemaAttendeeReadPublic.parse(data)) - .then((attendee) => res.status(200).json({ attendee })) - .catch((error: Error) => - res.status(404).json({ - message: `Attendee with id: ${safeQuery.data.id} not found`, - error, - }) - ); - break; - /** - * @swagger - * /attendees/{id}: - * patch: - * summary: Edit an existing attendee - * operationId: editAttendeeById - * requestBody: - * description: Edit an existing attendee related to one of your bookings - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * email: - * type: string - * example: email@example.com - * name: - * type: string - * example: John Doe - * timeZone: - * type: string - * example: Europe/London - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * example: 3 - * required: true - * description: ID of the attendee to edit - * tags: - * - attendees - * responses: - * 201: - * description: OK, attendee edited successfuly - * 400: - * description: Bad request. Attendee body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - case "PATCH": - const safeBody = schemaAttendeeEditBodyParams.safeParse(body); - if (!safeBody.success) { - res.status(400).json({ message: "Bad request", error: safeBody.error }); - return; - } - await prisma.attendee - .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) - .then((data) => schemaAttendeeReadPublic.parse(data)) - .then((attendee) => res.status(200).json({ attendee })) - .catch((error: Error) => - res.status(404).json({ - message: `Attendee with id: ${safeQuery.data.id} not found`, - error, - }) - ); - break; - /** - * @swagger - * /attendees/{id}: - * delete: - * operationId: removeAttendeeById - * summary: Remove an existing attendee - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the attendee to delete - * tags: - * - attendees - * responses: - * 201: - * description: OK, attendee removed successfuly - * 400: - * description: Bad request. Attendee id is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - case "DELETE": - await prisma.attendee - .delete({ where: { id: safeQuery.data.id } }) - .then(() => - res.status(200).json({ - message: `Attendee with id: ${safeQuery.data.id} deleted successfully`, - }) - ) - .catch((error: Error) => - res.status(404).json({ - message: `Attendee with id: ${safeQuery.data.id} not found`, - error, - }) - ); - break; - - default: - res.status(405).json({ message: "Method not allowed" }); - break; - } - } -} - -export default withMiddleware("HTTP_GET_DELETE_PATCH")(withValidQueryIdTransformParseInt(attendeeById)); diff --git a/pages/api/attendees/[id]/_auth-middleware.ts b/pages/api/attendees/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..b1b474b506 --- /dev/null +++ b/pages/api/attendees/[id]/_auth-middleware.ts @@ -0,0 +1,21 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const query = schemaQueryIdParseInt.parse(req.query); + // @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin. + if (isAdmin) return; + // Find all user bookings, including attendees + const attendee = await prisma.attendee.findFirst({ + where: { id: query.id, booking: { userId } }, + }); + // Flatten and merge all the attendees in one array + if (!attendee) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); +} + +export default defaultResponder(authMiddleware); diff --git a/pages/api/attendees/[id]/_delete.ts b/pages/api/attendees/[id]/_delete.ts new file mode 100644 index 0000000000..a51b6457b6 --- /dev/null +++ b/pages/api/attendees/[id]/_delete.ts @@ -0,0 +1,37 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * delete: + * operationId: removeAttendeeById + * summary: Remove an existing attendee + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the attendee to delete + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee removed successfuly + * 400: + * description: Bad request. Attendee id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await prisma.attendee.delete({ where: { id } }); + return { message: `Attendee with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/pages/api/attendees/[id]/_get.ts b/pages/api/attendees/[id]/_get.ts new file mode 100644 index 0000000000..aa10526544 --- /dev/null +++ b/pages/api/attendees/[id]/_get.ts @@ -0,0 +1,39 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeReadPublic } from "@lib/validations/attendee"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * get: + * operationId: getAttendeeById + * summary: Find an attendee + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the attendee to get + * example: 3 + * tags: + * - attendees + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Attendee was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const attendee = await prisma.attendee.findUnique({ where: { id } }); + return { attendee: schemaAttendeeReadPublic.parse(attendee) }; +} + +export default defaultResponder(getHandler); diff --git a/pages/api/attendees/[id]/_patch.ts b/pages/api/attendees/[id]/_patch.ts new file mode 100644 index 0000000000..77d3dc28d0 --- /dev/null +++ b/pages/api/attendees/[id]/_patch.ts @@ -0,0 +1,57 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeEditBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * patch: + * summary: Edit an existing attendee + * operationId: editAttendeeById + * requestBody: + * description: Edit an existing attendee related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * example: email@example.com + * name: + * type: string + * example: John Doe + * timeZone: + * type: string + * example: Europe/London + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * example: 3 + * required: true + * description: ID of the attendee to edit + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee edited successfuly + * 400: + * description: Bad request. Attendee body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaAttendeeEditBodyParams.parse(body); + const attendee = await prisma.attendee.update({ where: { id }, data }); + return { attendee: schemaAttendeeReadPublic.parse(attendee) }; +} + +export default defaultResponder(patchHandler); diff --git a/pages/api/attendees/[id]/index.ts b/pages/api/attendees/[id]/index.ts new file mode 100644 index 0000000000..4b740bcbb5 --- /dev/null +++ b/pages/api/attendees/[id]/index.ts @@ -0,0 +1,16 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "@lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware("HTTP_GET_DELETE_PATCH")(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req, res); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); +}); diff --git a/pages/api/attendees/_get.ts b/pages/api/attendees/_get.ts new file mode 100644 index 0000000000..078e8174a8 --- /dev/null +++ b/pages/api/attendees/_get.ts @@ -0,0 +1,34 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeReadPublic } from "@lib/validations/attendee"; + +/** + * @swagger + * /attendees: + * get: + * operationId: listAttendees + * summary: Find all attendees + * tags: + * - attendees + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No attendees were found + */ +async function handler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.AttendeeFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } }; + const data = await prisma.attendee.findMany(args); + const attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee)); + if (!attendees) throw new HttpError({ statusCode: 404, message: "No attendees were found" }); + return { attendees }; +} + +export default defaultResponder(handler); diff --git a/pages/api/attendees/_post.ts b/pages/api/attendees/_post.ts new file mode 100644 index 0000000000..1e5ff8199f --- /dev/null +++ b/pages/api/attendees/_post.ts @@ -0,0 +1,77 @@ +import { HttpError } from "@/../../packages/lib/http-error"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee"; + +/** + * @swagger + * /attendees: + * post: + * operationId: addAttendee + * summary: Creates a new attendee + * requestBody: + * description: Create a new attendee related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - bookingId + * - name + * - email + * - timeZone + * properties: + * bookingId: + * type: number + * example: 1 + * email: + * type: string + * example: email@example.com + * name: + * type: string + * example: John Doe + * timeZone: + * type: string + * example: Europe/London + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee created + * 400: + * description: Bad request. Attendee body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const body = schemaAttendeeCreateBodyParams.parse(req.body); + + if (!isAdmin) { + const userBooking = await prisma.booking.findFirst({ + where: { userId, id: body.bookingId }, + select: { id: true }, + }); + // Here we make sure to only return attendee's of the user's own bookings. + if (!userBooking) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); + } + + const data = await prisma.attendee.create({ + data: { + email: body.email, + name: body.name, + timeZone: body.timeZone, + booking: { connect: { id: body.bookingId } }, + }, + }); + + return { + attendee: schemaAttendeeReadPublic.parse(data), + message: "Attendee created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/pages/api/attendees/index.ts b/pages/api/attendees/index.ts index 880c8d4d4b..c07846423f 100644 --- a/pages/api/attendees/index.ts +++ b/pages/api/attendees/index.ts @@ -1,144 +1,10 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { defaultHandler } from "@calcom/lib/server"; import { withMiddleware } from "@lib/helpers/withMiddleware"; -import type { AttendeeResponse, AttendeesResponse } from "@lib/types"; -import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee"; -async function createOrlistAllAttendees( - { method, userId, body, prisma, isAdmin }: NextApiRequest, - res: NextApiResponse -) { - let attendees; - if (!isAdmin) { - const userBookings = await prisma.booking.findMany({ - where: { - userId, - }, - include: { - attendees: true, - }, - }); - attendees = userBookings.map((booking) => booking.attendees).flat(); - } else { - const data = await prisma.attendee.findMany(); - attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee)); - } - if (method === "GET") { - /** - * @swagger - * /attendees: - * get: - * operationId: listAttendees - * summary: Find all attendees - * tags: - * - attendees - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: No attendees were found - */ - if (attendees) res.status(200).json({ attendees }); - else (error: Error) => res.status(400).json({ error }); - } else if (method === "POST") { - /** - * @swagger - * /attendees: - * post: - * operationId: addAttendee - * summary: Creates a new attendee - * requestBody: - * description: Create a new attendee related to one of your bookings - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - bookingId - * - name - * - email - * - timeZone - * properties: - * bookingId: - * type: number - * example: 1 - * email: - * type: string - * example: email@example.com - * name: - * type: string - * example: John Doe - * timeZone: - * type: string - * example: Europe/London - * tags: - * - attendees - * responses: - * 201: - * description: OK, attendee created - * 400: - * description: Bad request. Attendee body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - const safePost = schemaAttendeeCreateBodyParams.safeParse(body); - if (!safePost.success) { - res.status(400).json({ message: "Invalid request body", error: safePost.error }); - return; - } - if (!isAdmin) { - const userWithBookings = await prisma.user.findUnique({ - where: { id: userId }, - include: { bookings: true }, - }); - if (!userWithBookings) { - res.status(404).json({ message: "User not found" }); - return; - } - const userBookingIds = userWithBookings.bookings.map((booking: { id: number }) => booking.id).flat(); - // Here we make sure to only return attendee's of the user's own bookings. - if (!userBookingIds.includes(safePost.data.bookingId)) - res.status(401).json({ message: "Unauthorized" }); - else { - const data = await prisma.attendee.create({ - data: { - email: safePost.data.email, - name: safePost.data.name, - timeZone: safePost.data.timeZone, - booking: { connect: { id: safePost.data.bookingId } }, - }, - }); - const attendee = schemaAttendeeReadPublic.parse(data); - - if (attendee) { - res.status(201).json({ - attendee, - message: "Attendee created successfully", - }); - } else (error: Error) => res.status(400).json({ error }); - } - } else { - const data = await prisma.attendee.create({ - data: { - email: safePost.data.email, - name: safePost.data.name, - timeZone: safePost.data.timeZone, - booking: { connect: { id: safePost.data.bookingId } }, - }, - }); - const attendee = schemaAttendeeReadPublic.parse(data); - - if (attendee) { - res.status(201).json({ - attendee, - message: "Attendee created successfully", - }); - } else (error: Error) => res.status(400).json({ error }); - } - } else res.status(405).json({ message: `Method ${method} not allowed` }); -} - -export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllAttendees); +export default withMiddleware("HTTP_GET_OR_POST")( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +);