diff --git a/pages/api/bookings/[id].ts b/pages/api/bookings/[id].ts deleted file mode 100644 index 32379cb231..0000000000 --- a/pages/api/bookings/[id].ts +++ /dev/null @@ -1,168 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -import { withMiddleware } from "@lib/helpers/withMiddleware"; -import type { BookingResponse } from "@lib/types"; -import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "@lib/validations/booking"; -import { - schemaQueryIdParseInt, - withValidQueryIdTransformParseInt, -} from "@lib/validations/shared/queryIdTransformParseInt"; - -export async function bookingById( - { method, query, body, userId, prisma, isAdmin }: NextApiRequest, - res: NextApiResponse -) { - const safeQuery = schemaQueryIdParseInt.safeParse(query); - if (!safeQuery.success) { - res.status(400).json({ message: "Your query was invalid" }); - return; - } - const userWithBookings = await prisma.user.findUnique({ - where: { id: userId }, - include: { bookings: true }, - }); - if (!userWithBookings) throw new Error("User not found"); - const userBookingIds = userWithBookings.bookings.map((booking: { id: number }) => booking.id).flat(); - if (!isAdmin || !userBookingIds.includes(safeQuery.data.id)) - res.status(401).json({ message: "Unauthorized" }); - else { - switch (method) { - /** - * @swagger - * /bookings/{id}: - * get: - * summary: Find a booking - * operationId: getBookingById - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the booking to get - * tags: - * - bookings - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: Booking was not found - */ - case "GET": - await prisma.booking - .findUnique({ where: { id: safeQuery.data.id } }) - .then((data) => schemaBookingReadPublic.parse(data)) - .then((booking) => res.status(200).json({ booking })) - .catch((error: Error) => - res.status(404).json({ - message: `Booking with id: ${safeQuery.data.id} not found`, - error, - }) - ); - /** - * @swagger - * /bookings/{id}: - * patch: - * summary: Edit an existing booking - * operationId: editBookingById - * requestBody: - * description: Edit an existing booking related to one of your event-types - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * title: - * type: string - * example: 15min - * startTime: - * type: string - * example: 1970-01-01T17:00:00.000Z - * endTime: - * type: string - * example: 1970-01-01T17:00:00.000Z - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the booking to edit - * tags: - * - bookings - * responses: - * 201: - * description: OK, booking edited successfuly - * 400: - * description: Bad request. Booking body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - case "PATCH": - const safeBody = schemaBookingEditBodyParams.safeParse(body); - if (!safeBody.success) { - console.log(safeBody.error); - res.status(400).json({ message: "Bad request", error: safeBody.error }); - return; - } - return await prisma.booking - .update({ - where: { id: safeQuery.data.id }, - data: safeBody.data, - }) - .then((data) => schemaBookingReadPublic.parse(data)) - .then((booking) => res.status(200).json({ booking })) - .catch((error: Error) => - res.status(404).json({ - message: `Booking with id: ${safeQuery.data.id} not found`, - error, - }) - ); - /** - * @swagger - * /bookings/{id}: - * delete: - * summary: Remove an existing booking - * operationId: removeBookingById - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the booking to delete - * tags: - * - bookings - * responses: - * 201: - * description: OK, booking removed successfuly - * 400: - * description: Bad request. Booking id is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - case "DELETE": - return await prisma.booking - .delete({ where: { id: safeQuery.data.id } }) - .then(() => - res.status(200).json({ - message: `Booking with id: ${safeQuery.data.id} deleted successfully`, - }) - ) - .catch((error: Error) => - res.status(404).json({ - message: `Booking with id: ${safeQuery.data.id} not found`, - error, - }) - ); - - default: - return res.status(405).json({ message: "Method not allowed" }); - } - } -} - -export default withMiddleware("HTTP_GET_DELETE_PATCH")(withValidQueryIdTransformParseInt(bookingById)); diff --git a/pages/api/bookings/[id]/_auth-middleware.ts b/pages/api/bookings/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..ead390f413 --- /dev/null +++ b/pages/api/bookings/[id]/_auth-middleware.ts @@ -0,0 +1,25 @@ +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"; + +export async function authMiddleware(req: NextApiRequest) { + const { userId, prisma, isAdmin, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const userWithBookings = await prisma.user.findUnique({ + where: { id: userId }, + include: { bookings: true }, + }); + + if (!userWithBookings) throw new HttpError({ statusCode: 404, message: "User not found" }); + + const userBookingIds = userWithBookings.bookings.map((booking) => booking.id); + + if (!isAdmin && !userBookingIds.includes(id)) { + throw new HttpError({ statusCode: 401, message: "You are not authorized" }); + } +} + +export default defaultResponder(authMiddleware); diff --git a/pages/api/bookings/[id]/_delete.ts b/pages/api/bookings/[id]/_delete.ts new file mode 100644 index 0000000000..46014f5785 --- /dev/null +++ b/pages/api/bookings/[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 + * /bookings/{id}: + * delete: + * summary: Remove an existing booking + * operationId: removeBookingById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to delete + * tags: + * - bookings + * responses: + * 201: + * description: OK, booking removed successfuly + * 400: + * description: Bad request. Booking 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.booking.delete({ where: { id } }); + return { message: `Booking with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/pages/api/bookings/[id]/_get.ts b/pages/api/bookings/[id]/_get.ts new file mode 100644 index 0000000000..567312261a --- /dev/null +++ b/pages/api/bookings/[id]/_get.ts @@ -0,0 +1,38 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingReadPublic } from "@lib/validations/booking"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /bookings/{id}: + * get: + * summary: Find a booking + * operationId: getBookingById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to get + * tags: + * - bookings + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Booking was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const booking = await prisma.booking.findUnique({ where: { id } }); + return { booking: schemaBookingReadPublic.parse(booking) }; +} + +export default defaultResponder(getHandler); diff --git a/pages/api/bookings/[id]/_patch.ts b/pages/api/bookings/[id]/_patch.ts new file mode 100644 index 0000000000..d2700ae229 --- /dev/null +++ b/pages/api/bookings/[id]/_patch.ts @@ -0,0 +1,56 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "@lib/validations/booking"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /bookings/{id}: + * patch: + * summary: Edit an existing booking + * operationId: editBookingById + * requestBody: + * description: Edit an existing booking related to one of your event-types + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * title: + * type: string + * example: 15min + * startTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * endTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to edit + * tags: + * - bookings + * responses: + * 201: + * description: OK, booking edited successfuly + * 400: + * description: Bad request. Booking 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 = schemaBookingEditBodyParams.parse(body); + const booking = await prisma.booking.update({ where: { id }, data }); + return { booking: schemaBookingReadPublic.parse(booking) }; +} + +export default defaultResponder(patchHandler); diff --git a/pages/api/bookings/[id]/index.ts b/pages/api/bookings/[id]/index.ts new file mode 100644 index 0000000000..4b740bcbb5 --- /dev/null +++ b/pages/api/bookings/[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); +});