diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index 229ab5a14b..f8571c06c1 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import dayjs from "@calcom/dayjs"; import { _ScheduleModel as Schedule, _AvailabilityModel as Availability } from "@calcom/prisma/zod"; const schemaScheduleBaseBodyParams = Schedule.omit({ id: true }).partial(); @@ -26,6 +27,13 @@ export const schemaSchedulePublic = z z.object({ availability: z .array(Availability.pick({ id: true, eventTypeId: true, days: true, startTime: true, endTime: true })) + .transform((v) => + v.map((item) => ({ + ...item, + startTime: dayjs.utc(item.startTime).format("HH:mm:ss"), + endTime: dayjs.utc(item.endTime).format("HH:mm:ss"), + })) + ) .optional(), }) ); diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id]/index.ts similarity index 100% rename from pages/api/schedules/[id].ts rename to pages/api/schedules/[id]/index.ts diff --git a/pages/api/schedules/_get.ts b/pages/api/schedules/_get.ts new file mode 100644 index 0000000000..40d3c9a1a6 --- /dev/null +++ b/pages/api/schedules/_get.ts @@ -0,0 +1,59 @@ +import type { NextApiRequest } from "next"; +import { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSchedulePublic } from "@lib/validations/schedule"; + +export const schemaUserIds = z + .union([z.string(), z.array(z.string())]) + .transform((val) => (Array.isArray(val) ? val.map((v) => parseInt(v, 10)) : [parseInt(val, 10)])); + +/** + * @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 + */ +async function handler({ body, prisma, userId, isAdmin, query }: NextApiRequest) { + let userIds: number[] = [userId]; + + if (!isAdmin && query.userId) { + // throw 403 Forbidden when the userId is given but user is not an admin + throw new HttpError({ statusCode: 403 }); + } + // When isAdmin && userId is given parse it and use it instead of the current (admin) user. + else if (query.userId) { + const result = schemaUserIds.safeParse(query.userId); + if (result.success && result.data) { + userIds = result.data; + } + } + + 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)); + if (schedules) { + return { schedules }; + } + + throw new HttpError({ statusCode: 404, message: "No schedules were found" }); +} + +export default defaultResponder(handler); diff --git a/pages/api/schedules/_post.ts b/pages/api/schedules/_post.ts new file mode 100644 index 0000000000..cb6399d247 --- /dev/null +++ b/pages/api/schedules/_post.ts @@ -0,0 +1,58 @@ +import { HttpError } from "@/../../packages/lib/http-error"; +import type { NextApiRequest } from "next"; + +import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaCreateScheduleBodyParams, schemaSchedulePublic } from "@lib/validations/schedule"; + +/** + * @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. + */ +async function postHandler({ body, userId, isAdmin, prisma }: NextApiRequest) { + const parsedBody = schemaCreateScheduleBodyParams.parse(body); + if (parsedBody.userId && !isAdmin) { + throw new HttpError({ statusCode: 403 }); + } + + const data = await prisma.schedule.create({ + data: { + ...parsedBody, + userId: parsedBody.userId || userId, + availability: { + createMany: { + data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ + days: schedule.days, + startTime: schedule.startTime, + endTime: schedule.endTime, + })), + }, + }, + }, + }); + + const createSchedule = schemaSchedulePublic.safeParse(data); + if (!createSchedule.success) { + throw new HttpError({ statusCode: 400, message: "Could not create new schedule" }); + } + + return { + schedule: createSchedule.data, + message: "Schedule created succesfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index 10536f5cea..c07846423f 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -1,128 +1,10 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { defaultHandler } from "@calcom/lib/server"; -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 { - schemaScheduleBodyParams, - schemaSchedulePublic, - schemaCreateScheduleBodyParams, -} from "@lib/validations/schedule"; -async function createOrlistAllSchedules( - { method, 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 safe = schemaScheduleBodyParams.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; - } 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 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)); - 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 = schemaCreateScheduleBodyParams.safeParse(body); - if (body.userId && !isAdmin) { - res.status(401).json({ message: "Unauthorized" }); - return; - } - - if (!safe.success) { - res.status(400).json({ message: "Invalid request body" }); - return; - } - - const data = await prisma.schedule.create({ - data: { - ...safe.data, - userId: safe.data.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" }); - 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); +export default withMiddleware("HTTP_GET_OR_POST")( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +);