diff --git a/lib/validations/availability.ts b/lib/validations/availability.ts index a6ddf4b5c6..f55d481b2f 100644 --- a/lib/validations/availability.ts +++ b/lib/validations/availability.ts @@ -8,6 +8,7 @@ export const schemaAvailabilityBaseBodyParams = Availability.pick({ date: true, scheduleId: true, days: true, + userId: true, }).partial(); export const schemaAvailabilityReadPublic = Availability.pick({ @@ -17,9 +18,9 @@ export const schemaAvailabilityReadPublic = Availability.pick({ date: true, scheduleId: true, days: true, - userId: true, eventTypeId: true, -}); + userId: true, +}).merge(z.object({ success: z.boolean().optional() })); const schemaAvailabilityCreateParams = z .object({ @@ -45,3 +46,13 @@ export const schemaAvailabilityEditBodyParams = schemaAvailabilityBaseBodyParams export const schemaAvailabilityCreateBodyParams = schemaAvailabilityBaseBodyParams.merge( schemaAvailabilityCreateParams ); + +export const schemaAvailabilityReadBodyParams = z + .object({ + userId: z.union([z.number(), z.array(z.number())]), + }) + .partial(); + +export const schemaSingleAvailabilityReadBodyParams = z.object({ + userId: z.number(), +}); diff --git a/pages/api/availabilities/[id].ts b/pages/api/availabilities/[id].ts index 1d4b6ed4a1..d95c10cc18 100644 --- a/pages/api/availabilities/[id].ts +++ b/pages/api/availabilities/[id].ts @@ -1,10 +1,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import safeParseJSON from "@lib/helpers/safeParseJSON"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import type { AvailabilityResponse } from "@lib/types"; import { schemaAvailabilityEditBodyParams, schemaAvailabilityReadPublic, + schemaSingleAvailabilityReadBodyParams, } from "@lib/validations/availability"; import { schemaQueryIdParseInt, @@ -12,18 +14,47 @@ import { } from "@lib/validations/shared/queryIdTransformParseInt"; export async function availabilityById( - { method, query, body, userId, prisma }: NextApiRequest, + { method, query, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { + body = safeParseJSON(body); + if (body.success !== undefined && !body.success) { + res.status(400).json({ message: body.message }); + return; + } + const safeQuery = schemaQueryIdParseInt.safeParse(query); if (!safeQuery.success) { res.status(400).json({ message: "Your query is invalid", error: safeQuery.error }); return; } - const data = await prisma.availability.findMany({ where: { userId } }); - const availabiltiesIds = data.map((availability) => availability.id); - if (!availabiltiesIds.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" }); - else { + + const safe = schemaSingleAvailabilityReadBodyParams.safeParse(body); + if (!safe.success) { + res.status(400).json({ message: "Bad request" }); + return; + } + + const safeBody = safe.data; + + if (safeBody.userId && !isAdmin) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + + const data = await prisma.schedule.findMany({ + where: { userId: safeBody.userId || userId }, + select: { + availability: true, + }, + }); + + const availabilitiesArray = data.flatMap((schedule) => schedule.availability); + + if (!availabilitiesArray.some((availability) => availability.id === safeQuery.data.id)) { + res.status(401).json({ message: "Unauthorized" }); + return; + } else { switch (method) { /** * @swagger @@ -100,7 +131,6 @@ export async function availabilityById( * description: Authorization information is missing or invalid. */ case "PATCH": - console.log(body); const safeBody = schemaAvailabilityEditBodyParams.safeParse(body); if (!safeBody.success) { console.log(safeBody.error); diff --git a/pages/api/availabilities/index.ts b/pages/api/availabilities/index.ts index ab4ae8f73b..e3bc338f9f 100644 --- a/pages/api/availabilities/index.ts +++ b/pages/api/availabilities/index.ts @@ -1,101 +1,140 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import safeParseJSON from "@lib/helpers/safeParseJSON"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import { AvailabilityResponse, AvailabilitiesResponse } from "@lib/types"; import { schemaAvailabilityCreateBodyParams, schemaAvailabilityReadPublic, + schemaAvailabilityReadBodyParams, } from "@lib/validations/availability"; async function createOrlistAllAvailabilities( - { method, body, userId, prisma }: NextApiRequest, + { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - if (method === "GET") { - /** - * @swagger - * /availabilities: - * get: - * operationId: listAvailabilities - * summary: Find all availabilities - * tags: - * - availabilities - * externalDocs: - * url: https://docs.cal.com/availability - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: No availabilities were found - */ - const data = await prisma.availability.findMany({ where: { userId } }); - const availabilities = data.map((availability) => schemaAvailabilityReadPublic.parse(availability)); - if (availabilities) res.status(200).json({ availabilities }); - else - (error: Error) => - res.status(404).json({ - message: "No Availabilities were found", - error, - }); - } else if (method === "POST") { - /** - * @swagger - * /availabilities: - * post: - * operationId: addAvailability - * summary: Creates a new availability - * requestBody: - * description: Edit an existing availability related to one of your bookings - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - startTime - * - endTime - * properties: - * days: - * type: array - * example: email@example.com - * startTime: - * type: string - * example: 1970-01-01T17:00:00.000Z - * endTime: - * type: string - * example: 1970-01-01T17:00:00.000Z - * tags: - * - availabilities - * externalDocs: - * url: https://docs.cal.com/availability - * responses: - * 201: - * description: OK, availability created - * 400: - * description: Bad request. Availability body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - const safe = schemaAvailabilityCreateBodyParams.safeParse(body); - if (!safe.success) { - res.status(400).json({ message: "Your request is invalid", error: safe.error }); - return; - } - // FIXME: check for eventTypeId ad scheduleId ownership if passed + body = safeParseJSON(body); + if (body.success !== undefined && !body.success) { + res.status(400).json({ message: body.message }); + return; + } - const data = await prisma.availability.create({ data: { ...safe.data, userId } }); - const availability = schemaAvailabilityReadPublic.parse(data); + const safe = schemaAvailabilityReadBodyParams.safeParse(body); - if (availability) res.status(201).json({ availability, message: "Availability created successfully" }); - else - (error: Error) => - res.status(400).json({ - message: "Could not create new availability", - error, - }); - } else res.status(405).json({ message: `Method ${method} not allowed` }); + if (!safe.success) { + return res.status(400).json({ message: "Bad request" }); + } + + const safeBody = safe.data; + + if (safeBody.userId && !isAdmin) { + res.status(401).json({ message: "Unauthorized" }); + return; + } else { + if (method === "GET") { + /** + * @swagger + * /availabilities: + * get: + * operationId: listAvailabilities + * summary: Find all availabilities + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No availabilities were found + */ + // const data = await prisma.availability.findMany({ where: { userId } }); + + const userIds = Array.isArray(safeBody.userId) ? safeBody.userId : [safeBody.userId || userId]; + + const schedules = await prisma.schedule.findMany({ + where: { + userId: { in: userIds }, + availability: { some: {} }, + }, + select: { + availability: true, + userId: true, + }, + ...(Array.isArray(body.userId) && { orderBy: { userId: "asc" } }), + }); + + const availabilities = schedules.flatMap((schedule) => { + return { ...schedule.availability[0], userId: schedule.userId }; + }); + + if (availabilities) res.status(200).json({ availabilities }); + else + (error: Error) => + res.status(404).json({ + message: "No Availabilities were found", + error, + }); + } else if (method === "POST") { + /** + * @swagger + * /availabilities: + * post: + * operationId: addAvailability + * summary: Creates a new availability + * requestBody: + * description: Edit an existing availability related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - startTime + * - endTime + * properties: + * days: + * type: array + * example: email@example.com + * startTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * endTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 201: + * description: OK, availability created + * 400: + * description: Bad request. Availability body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + const safe = schemaAvailabilityCreateBodyParams.safeParse(body); + if (!safe.success) { + res.status(400).json({ message: "Your request is invalid", error: safe.error }); + return; + } + // FIXME: check for eventTypeId ad scheduleId ownership if passed + + const data = await prisma.availability.create({ data: { ...safe.data, userId } }); + const availability = schemaAvailabilityReadPublic.parse(data); + + if (availability) res.status(201).json({ availability, message: "Availability created successfully" }); + else + (error: Error) => + res.status(400).json({ + message: "Could not create new availability", + error, + }); + } else res.status(405).json({ message: `Method ${method} not allowed` }); + } } export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllAvailabilities);