From 1adace1c0d98b3f3c1a9f6a3132c68285893bd91 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 5 Oct 2022 09:56:27 -0400 Subject: [PATCH 01/19] Create new availability on new schedule --- lib/validations/schedule.ts | 2 +- pages/api/schedules/index.ts | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index 176d972da1..e477087055 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -5,7 +5,7 @@ import { _ScheduleModel as Schedule } from "@calcom/prisma/zod"; const schemaScheduleBaseBodyParams = Schedule.omit({ id: true }).partial(); const schemaScheduleRequiredParams = z.object({ - userId: z.number(), + userId: z.number().optional(), name: z.string(), }); diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 81964f4030..e4e2b69105 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -1,11 +1,13 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { getAvailabilityFromSchedule, DEFAULT_SCHEDULE } from "@calcom/lib/availability"; + import { withMiddleware } from "@lib/helpers/withMiddleware"; import { ScheduleResponse, SchedulesResponse } from "@lib/types"; import { schemaScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; async function createOrlistAllSchedules( - { method, body, userId, prisma }: NextApiRequest, + { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { if (method === "GET") { @@ -25,7 +27,10 @@ async function createOrlistAllSchedules( * 404: * description: No schedules were found */ - const data = await prisma.schedule.findMany({ where: { userId } }); + if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); + const data = await prisma.schedule.findMany({ + where: { userId: body.userId && isAdmin ? body.userId : userId }, + }); const schedules = data.map((schedule) => schemaSchedulePublic.parse(schedule)); if (schedules) res.status(200).json({ schedules }); else @@ -52,11 +57,27 @@ async function createOrlistAllSchedules( * description: Authorization information is missing or invalid. */ const safe = schemaScheduleBodyParams.safeParse(body); + if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); + if (!safe.success) { res.status(400).json({ message: "Invalid request body" }); return; } - const data = await prisma.schedule.create({ data: { ...safe.data, userId } }); + const data = await prisma.schedule.create({ + data: { + ...safe.data, + userId: body.userId && isAdmin ? body.userId : userId, + availability: { + createMany: { + data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ + days: schedule.days, + startTime: schedule.startTime, + endTime: schedule.endTime, + })), + }, + }, + }, + }); const schedule = schemaSchedulePublic.parse(data); if (schedule) res.status(201).json({ schedule, message: "Schedule created successfully" }); From 95fc04a45316ce09161d925cde0231e4867e5a12 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 5 Oct 2022 10:05:04 -0400 Subject: [PATCH 02/19] Add check for userId and admin to top --- pages/api/schedules/index.ts | 151 ++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index e4e2b69105..8a23a6c121 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -10,84 +10,87 @@ async function createOrlistAllSchedules( { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - if (method === "GET") { - /** - * @swagger - * /schedules: - * get: - * operationId: listSchedules - * summary: Find all schedules - * tags: - * - schedules - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: No schedules were found - */ - if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); - const data = await prisma.schedule.findMany({ - where: { userId: body.userId && isAdmin ? body.userId : userId }, - }); - const schedules = data.map((schedule) => schemaSchedulePublic.parse(schedule)); - if (schedules) res.status(200).json({ schedules }); - else - (error: Error) => - res.status(404).json({ - message: "No Schedules were found", - error, - }); - } else if (method === "POST") { - /** - * @swagger - * /schedules: - * post: - * operationId: addSchedule - * summary: Creates a new schedule - * tags: - * - schedules - * responses: - * 201: - * description: OK, schedule created - * 400: - * description: Bad request. Schedule body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - const safe = schemaScheduleBodyParams.safeParse(body); - if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); + if (body.userId && !isAdmin) { + res.status(401).json({ message: "Unauthorized" }); + } else { + if (method === "GET") { + /** + * @swagger + * /schedules: + * get: + * operationId: listSchedules + * summary: Find all schedules + * tags: + * - schedules + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No schedules were found + */ + const data = await prisma.schedule.findMany({ + where: { userId: body.userId && isAdmin ? body.userId : userId }, + }); + const schedules = data.map((schedule) => schemaSchedulePublic.parse(schedule)); + if (schedules) res.status(200).json({ schedules }); + else + (error: Error) => + res.status(404).json({ + message: "No Schedules were found", + error, + }); + } else if (method === "POST") { + /** + * @swagger + * /schedules: + * post: + * operationId: addSchedule + * summary: Creates a new schedule + * tags: + * - schedules + * responses: + * 201: + * description: OK, schedule created + * 400: + * description: Bad request. Schedule body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + const safe = schemaScheduleBodyParams.safeParse(body); + if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); - if (!safe.success) { - res.status(400).json({ message: "Invalid request body" }); - return; - } - const data = await prisma.schedule.create({ - data: { - ...safe.data, - userId: body.userId && isAdmin ? body.userId : userId, - availability: { - createMany: { - data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ - days: schedule.days, - startTime: schedule.startTime, - endTime: schedule.endTime, - })), + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } + const data = await prisma.schedule.create({ + data: { + ...safe.data, + userId: body.userId && isAdmin ? body.userId : userId, + availability: { + createMany: { + data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ + days: schedule.days, + startTime: schedule.startTime, + endTime: schedule.endTime, + })), + }, }, }, - }, - }); - const schedule = schemaSchedulePublic.parse(data); + }); + const schedule = schemaSchedulePublic.parse(data); - if (schedule) res.status(201).json({ schedule, message: "Schedule created successfully" }); - else - (error: Error) => - res.status(400).json({ - message: "Could not create new schedule", - error, - }); - } else res.status(405).json({ message: `Method ${method} not allowed` }); + if (schedule) res.status(201).json({ schedule, message: "Schedule created successfully" }); + else + (error: Error) => + res.status(400).json({ + message: "Could not create new schedule", + error, + }); + } else res.status(405).json({ message: `Method ${method} not allowed` }); + } } export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllSchedules); From a5413b40abac79885f8e1ade219a29312e2df099 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 5 Oct 2022 11:04:58 -0400 Subject: [PATCH 03/19] Add ability to get, post, and delete for other users if admin --- lib/validations/schedule.ts | 10 ++++++++-- pages/api/schedules/[id].ts | 10 +++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index e477087055..4e8e983094 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -5,10 +5,16 @@ import { _ScheduleModel as Schedule } from "@calcom/prisma/zod"; const schemaScheduleBaseBodyParams = Schedule.omit({ id: true }).partial(); const schemaScheduleRequiredParams = z.object({ - userId: z.number().optional(), name: z.string(), }); export const schemaScheduleBodyParams = schemaScheduleBaseBodyParams.merge(schemaScheduleRequiredParams); -export const schemaSchedulePublic = Schedule.omit({}); +export const schemaSchedulePublic = z + .object({ id: z.number() }) + .merge(Schedule) + .merge( + z.object({ + availability: z.array(z.object({ id: z.number() })).optional(), + }) + ); diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 593f9d7846..834a7138e2 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -9,16 +9,17 @@ import { } from "@lib/validations/shared/queryIdTransformParseInt"; export async function scheduleById( - { method, query, body, userId, prisma }: NextApiRequest, + { method, query, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { + if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); const safeQuery = schemaQueryIdParseInt.safeParse(query); const safeBody = schemaScheduleBodyParams.safeParse(body); if (!safeQuery.success) { res.status(400).json({ message: "Your query was invalid" }); return; } - const userSchedules = await prisma.schedule.findMany({ where: { userId } }); + const userSchedules = await prisma.schedule.findMany({ where: { userId: body.userId || userId } }); const userScheduleIds = userSchedules.map((schedule) => schedule.id); if (!userScheduleIds.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" }); else { @@ -48,7 +49,10 @@ export async function scheduleById( */ case "GET": await prisma.schedule - .findUnique({ where: { id: safeQuery.data.id } }) + .findUnique({ + where: { id: safeQuery.data.id }, + include: { availability: { select: { id: true } } }, + }) .then((data) => schemaSchedulePublic.parse(data)) .then((schedule) => res.status(200).json({ schedule })) .catch((error: Error) => From 77b89fda05628bec3eaed2d0b2abc20261c0046b Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 5 Oct 2022 16:02:34 -0400 Subject: [PATCH 04/19] Add return statements --- pages/api/schedules/[id].ts | 11 ++++++++--- pages/api/schedules/index.ts | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 834a7138e2..5fab15f833 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -12,7 +12,10 @@ export async function scheduleById( { method, query, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); + if (body.userId && !isAdmin) { + res.status(401).json({ message: "Unauthorized" }); + return; + } const safeQuery = schemaQueryIdParseInt.safeParse(query); const safeBody = schemaScheduleBodyParams.safeParse(body); if (!safeQuery.success) { @@ -21,8 +24,10 @@ export async function scheduleById( } const userSchedules = await prisma.schedule.findMany({ where: { userId: body.userId || userId } }); const userScheduleIds = userSchedules.map((schedule) => schedule.id); - if (!userScheduleIds.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" }); - else { + if (!userScheduleIds.includes(safeQuery.data.id)) { + res.status(401).json({ message: "Unauthorized" }); + return; + } else { switch (method) { /** * @swagger diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 8a23a6c121..0a35657d09 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -12,6 +12,7 @@ async function createOrlistAllSchedules( ) { if (body.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); + return; } else { if (method === "GET") { /** @@ -59,7 +60,10 @@ async function createOrlistAllSchedules( * description: Authorization information is missing or invalid. */ const safe = schemaScheduleBodyParams.safeParse(body); - if (body.userId && !isAdmin) res.status(401).json({ message: "Unauthorized" }); + if (body.userId && !isAdmin) { + res.status(401).json({ message: "Unauthorized" }); + return; + } if (!safe.success) { res.status(400).json({ message: "Invalid request body" }); From 372e1882287202156eb78287ba0fb6ea1d8dd020 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 6 Oct 2022 09:55:34 -0400 Subject: [PATCH 05/19] Pass userId as a single value or an array --- pages/api/schedules/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 0a35657d09..29b5577c51 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -32,7 +32,13 @@ async function createOrlistAllSchedules( * description: No schedules were found */ const data = await prisma.schedule.findMany({ - where: { userId: body.userId && isAdmin ? body.userId : userId }, + // where: { userId: body.userId && isAdmin ? body.userId : userId }, + where: { + ...(Array.isArray(body.userId) + ? { userId: { in: body.userId } } + : { userId: body.userId || userId }), + }, + ...(Array.isArray(body.userId) && { orderBy: { userId: "asc" } }), }); const schedules = data.map((schedule) => schemaSchedulePublic.parse(schedule)); if (schedules) res.status(200).json({ schedules }); From 4ac9c1e6dd4be13f6f35fb79e9f2d26cd29a742b Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 6 Oct 2022 10:02:35 -0400 Subject: [PATCH 06/19] Remove old comment --- pages/api/schedules/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 29b5577c51..d6156c9c8b 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -32,7 +32,6 @@ async function createOrlistAllSchedules( * description: No schedules were found */ const data = await prisma.schedule.findMany({ - // where: { userId: body.userId && isAdmin ? body.userId : userId }, where: { ...(Array.isArray(body.userId) ? { userId: { in: body.userId } } From 2a7a111855411ae156a3d69b231066cc9464cfd7 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 6 Oct 2022 14:38:17 -0400 Subject: [PATCH 07/19] Safe parse for userId --- lib/validations/schedule.ts | 9 ++++++--- pages/api/schedules/[id].ts | 14 +++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index 4e8e983094..32edcdae63 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -1,11 +1,12 @@ import { z } from "zod"; -import { _ScheduleModel as Schedule } from "@calcom/prisma/zod"; +import { _ScheduleModel as Schedule, _AvailabilityModel as Availability } from "@calcom/prisma/zod"; const schemaScheduleBaseBodyParams = Schedule.omit({ id: true }).partial(); const schemaScheduleRequiredParams = z.object({ - name: z.string(), + name: z.string().optional(), + userId: z.union([z.number(), z.array(z.number())]).optional(), }); export const schemaScheduleBodyParams = schemaScheduleBaseBodyParams.merge(schemaScheduleRequiredParams); @@ -15,6 +16,8 @@ export const schemaSchedulePublic = z .merge(Schedule) .merge( z.object({ - availability: z.array(z.object({ id: z.number() })).optional(), + availability: z + .array(Availability.pick({ id: true, eventTypeId: true, days: true, startTime: true, endTime: true })) + .optional(), }) ); diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 5fab15f833..f92392fa3f 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -12,17 +12,18 @@ export async function scheduleById( { method, query, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - if (body.userId && !isAdmin) { + const safeQuery = schemaQueryIdParseInt.safeParse(query); + const safeBody = schemaScheduleBodyParams.safeParse(body); + + if (safeBody.data.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); return; } - const safeQuery = schemaQueryIdParseInt.safeParse(query); - const safeBody = schemaScheduleBodyParams.safeParse(body); if (!safeQuery.success) { res.status(400).json({ message: "Your query was invalid" }); return; } - const userSchedules = await prisma.schedule.findMany({ where: { userId: body.userId || userId } }); + const userSchedules = await prisma.schedule.findMany({ where: { userId: safeBody.data.userId || userId } }); const userScheduleIds = userSchedules.map((schedule) => schedule.id); if (!userScheduleIds.includes(safeQuery.data.id)) { res.status(401).json({ message: "Unauthorized" }); @@ -56,7 +57,7 @@ export async function scheduleById( await prisma.schedule .findUnique({ where: { id: safeQuery.data.id }, - include: { availability: { select: { id: true } } }, + include: { availability: true }, }) .then((data) => schemaSchedulePublic.parse(data)) .then((schedule) => res.status(200).json({ schedule })) @@ -98,6 +99,9 @@ export async function scheduleById( return; } } + + delete safeBody.data.userId; + await prisma.schedule .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) .then((data) => schemaSchedulePublic.parse(data)) From 00ccb4ffd102d1e22b75ec20016ae10ef0fb148b Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 6 Oct 2022 14:41:50 -0400 Subject: [PATCH 08/19] Simplify get /schedules --- pages/api/schedules/index.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index d6156c9c8b..b486431440 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -10,7 +10,9 @@ async function createOrlistAllSchedules( { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - if (body.userId && !isAdmin) { + const safeBody = schemaScheduleBodyParams.safeParse(body); + + if (safeBody.data.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); return; } else { @@ -31,11 +33,14 @@ async function createOrlistAllSchedules( * 404: * description: No schedules were found */ + + const userIds = Array.isArray(safeBody.data.userId) + ? safeBody.data.userId + : [safeBody.data.userId || userId]; + const data = await prisma.schedule.findMany({ where: { - ...(Array.isArray(body.userId) - ? { userId: { in: body.userId } } - : { userId: body.userId || userId }), + userId: { in: userIds }, }, ...(Array.isArray(body.userId) && { orderBy: { userId: "asc" } }), }); From da88beb1f5b31755bacd32a2f3cb2dbf4f443f97 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 6 Oct 2022 15:06:07 -0400 Subject: [PATCH 09/19] Fix type errors --- lib/validations/schedule.ts | 8 ++++++++ pages/api/schedules/[id].ts | 8 ++++++-- pages/api/schedules/index.ts | 27 +++++++++++++++++++-------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index 32edcdae63..229ab5a14b 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -11,6 +11,14 @@ const schemaScheduleRequiredParams = z.object({ export const schemaScheduleBodyParams = schemaScheduleBaseBodyParams.merge(schemaScheduleRequiredParams); +export const schemaSingleScheduleBodyParams = schemaScheduleBaseBodyParams.merge( + z.object({ userId: z.number().optional() }) +); + +export const schemaCreateScheduleBodyParams = schemaScheduleBaseBodyParams.merge( + z.object({ userId: z.number().optional(), name: z.string() }) +); + export const schemaSchedulePublic = z .object({ id: z.number() }) .merge(Schedule) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index f92392fa3f..adc26e7b6d 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import type { ScheduleResponse } from "@lib/types"; -import { schemaScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; +import { schemaSingleScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt, @@ -13,7 +13,11 @@ export async function scheduleById( res: NextApiResponse ) { const safeQuery = schemaQueryIdParseInt.safeParse(query); - const safeBody = schemaScheduleBodyParams.safeParse(body); + const safeBody = schemaSingleScheduleBodyParams.safeParse(body); + if (!safeBody.success) { + res.status(400).json({ message: "Bad request" }); + return; + } if (safeBody.data.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index b486431440..7380a4faa2 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -4,15 +4,26 @@ import { getAvailabilityFromSchedule, DEFAULT_SCHEDULE } from "@calcom/lib/avail import { withMiddleware } from "@lib/helpers/withMiddleware"; import { ScheduleResponse, SchedulesResponse } from "@lib/types"; -import { schemaScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; +import { + schemaScheduleBodyParams, + schemaSchedulePublic, + schemaCreateScheduleBodyParams, +} from "@lib/validations/schedule"; async function createOrlistAllSchedules( { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - const safeBody = schemaScheduleBodyParams.safeParse(body); + const safe = schemaScheduleBodyParams.safeParse(body); - if (safeBody.data.userId && !isAdmin) { + 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; } else { @@ -34,14 +45,13 @@ async function createOrlistAllSchedules( * description: No schedules were found */ - const userIds = Array.isArray(safeBody.data.userId) - ? safeBody.data.userId - : [safeBody.data.userId || userId]; + const userIds = Array.isArray(safeBody.userId) ? safeBody.userId : [safeBody.userId || userId]; const data = await prisma.schedule.findMany({ where: { userId: { in: userIds }, }, + include: { availability: true }, ...(Array.isArray(body.userId) && { orderBy: { userId: "asc" } }), }); const schedules = data.map((schedule) => schemaSchedulePublic.parse(schedule)); @@ -69,7 +79,7 @@ async function createOrlistAllSchedules( * 401: * description: Authorization information is missing or invalid. */ - const safe = schemaScheduleBodyParams.safeParse(body); + const safe = schemaCreateScheduleBodyParams.safeParse(body); if (body.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); return; @@ -79,10 +89,11 @@ async function createOrlistAllSchedules( res.status(400).json({ message: "Invalid request body" }); return; } + const data = await prisma.schedule.create({ data: { ...safe.data, - userId: body.userId && isAdmin ? body.userId : userId, + userId: safe.data.userId || userId, availability: { createMany: { data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ From 12f54773f9243a9c45a75f14571d1657bb21c2d3 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:43:47 +0530 Subject: [PATCH 10/19] Adds safeParseJSON.ts file --- lib/helpers/safeParseJSON.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/helpers/safeParseJSON.ts diff --git a/lib/helpers/safeParseJSON.ts b/lib/helpers/safeParseJSON.ts new file mode 100644 index 0000000000..f86e8539e0 --- /dev/null +++ b/lib/helpers/safeParseJSON.ts @@ -0,0 +1,8 @@ +export default function parseJSONSafely(str: string) { + try { + return JSON.parse(str); + } catch (e) { + console.error((e as Error).message); + return {}; + } +} From 36a0ebfbadf7db444f2540f60729d9534dce2e45 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:46:35 +0530 Subject: [PATCH 11/19] Adds safe json parse of the body It ensures that the body complies with the safe parsing of the JSON so that if the body sent is not a valid JSON, we convert it into an empty JSON. Would improve in handling the response of such cases as a follow up for improved UX --- pages/api/schedules/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 7380a4faa2..e1b8d79350 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getAvailabilityFromSchedule, DEFAULT_SCHEDULE } from "@calcom/lib/availability"; +import safeParseJSON from "@lib/helpers/safeParseJSON"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import { ScheduleResponse, SchedulesResponse } from "@lib/types"; import { @@ -14,7 +15,7 @@ async function createOrlistAllSchedules( { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - const safe = schemaScheduleBodyParams.safeParse(body); + const safe = schemaScheduleBodyParams.safeParse(safeParseJSON(body)); if (!safe.success) { res.status(400).json({ message: "Bad request" }); From 79fabe0333e567e36ac42edcfc04ba2131d93026 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:03:09 +0530 Subject: [PATCH 12/19] Adds safeParseJSON to the body --- pages/api/schedules/[id].ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index adc26e7b6d..677c8d8975 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import safeParseJSON from "@lib/helpers/safeParseJSON"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import type { ScheduleResponse } from "@lib/types"; import { schemaSingleScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; @@ -13,7 +14,7 @@ export async function scheduleById( res: NextApiResponse ) { const safeQuery = schemaQueryIdParseInt.safeParse(query); - const safeBody = schemaSingleScheduleBodyParams.safeParse(body); + const safeBody = schemaSingleScheduleBodyParams.safeParse(safeParseJSON(body)); if (!safeBody.success) { res.status(400).json({ message: "Bad request" }); return; From 3bdebba21ecb90901839d7acceaa6f854a97670c Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:12:57 +0530 Subject: [PATCH 13/19] Adds meaningful response for the API caller --- lib/helpers/safeParseJSON.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/helpers/safeParseJSON.ts b/lib/helpers/safeParseJSON.ts index f86e8539e0..3681e42273 100644 --- a/lib/helpers/safeParseJSON.ts +++ b/lib/helpers/safeParseJSON.ts @@ -3,6 +3,12 @@ export default function parseJSONSafely(str: string) { return JSON.parse(str); } catch (e) { console.error((e as Error).message); + if ((e as Error).message.includes("Unexpected token")) { + return { + success: false, + message: `Invalid JSON in the body: ${(e as Error).message}`, + }; + } return {}; } } From affba8bf66013bf2e9250f6332b9992dbdc7a873 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:13:56 +0530 Subject: [PATCH 14/19] Adds meaningful response for API caller --- pages/api/schedules/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index e1b8d79350..3b8abc776f 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -15,7 +15,12 @@ async function createOrlistAllSchedules( { method, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - const safe = schemaScheduleBodyParams.safeParse(safeParseJSON(body)); + body = safeParseJSON(body); + if (!body.success) { + res.status(400).json({ message: body.message }); + } + + const safe = schemaScheduleBodyParams.safeParse(body); if (!safe.success) { res.status(400).json({ message: "Bad request" }); From 21e081c64c1525395752d4f5d98aa47612f459e9 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:15:35 +0530 Subject: [PATCH 15/19] Adds meaningful response for API caller --- pages/api/schedules/[id].ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 677c8d8975..4ef7a85b6d 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -13,13 +13,18 @@ export async function scheduleById( { method, query, body, userId, isAdmin, prisma }: NextApiRequest, res: NextApiResponse ) { - const safeQuery = schemaQueryIdParseInt.safeParse(query); - const safeBody = schemaSingleScheduleBodyParams.safeParse(safeParseJSON(body)); + body = safeParseJSON(body); + if (!body.success) { + res.status(400).json({ message: body.message }); + } + + const safe = schemaScheduleBodyParams.safeParse(body); if (!safeBody.success) { res.status(400).json({ message: "Bad request" }); return; } - + + const safeQuery = schemaQueryIdParseInt.safeParse(query); if (safeBody.data.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" }); return; From 9a65c547dcab2dde15668a65420f658c1fb8d069 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:24:55 +0530 Subject: [PATCH 16/19] Adds missing return after res --- pages/api/schedules/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 3b8abc776f..86faad4181 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -18,6 +18,7 @@ async function createOrlistAllSchedules( body = safeParseJSON(body); if (!body.success) { res.status(400).json({ message: body.message }); + return; } const safe = schemaScheduleBodyParams.safeParse(body); From 8eaad0c1c6976f0954f0a7953799d65890e5f3f9 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:34:02 +0530 Subject: [PATCH 17/19] fixed typo --- pages/api/schedules/[id].ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 4ef7a85b6d..384e1f37c3 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -18,7 +18,7 @@ export async function scheduleById( res.status(400).json({ message: body.message }); } - const safe = schemaScheduleBodyParams.safeParse(body); + const safe = schemaSingleScheduleBodyParams.safeParse(body); if (!safeBody.success) { res.status(400).json({ message: "Bad request" }); return; From b05b6e48e785176b5da470e75b34b1be9ffffdec Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:41:17 +0530 Subject: [PATCH 18/19] Fixes another typo --- pages/api/schedules/[id].ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 384e1f37c3..31182fabda 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -18,7 +18,7 @@ export async function scheduleById( res.status(400).json({ message: body.message }); } - const safe = schemaSingleScheduleBodyParams.safeParse(body); + const safeBody = schemaSingleScheduleBodyParams.safeParse(body); if (!safeBody.success) { res.status(400).json({ message: "Bad request" }); return; From 628306793d42d885ba7a37347d6cad6a40a2633f Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:47:46 +0530 Subject: [PATCH 19/19] Prettier fix :/ --- pages/api/schedules/[id].ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 31182fabda..59c78ea12b 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -23,7 +23,7 @@ export async function scheduleById( res.status(400).json({ message: "Bad request" }); return; } - + const safeQuery = schemaQueryIdParseInt.safeParse(query); if (safeBody.data.userId && !isAdmin) { res.status(401).json({ message: "Unauthorized" });