diff --git a/lib/validations/event-type-custom-input.ts b/lib/validations/event-type-custom-input.ts index 560d3d7684..8a9fb3dbbe 100644 --- a/lib/validations/event-type-custom-input.ts +++ b/lib/validations/event-type-custom-input.ts @@ -1,27 +1,13 @@ -import { z } from "zod"; - import { _EventTypeCustomInputModel as EventTypeCustomInput } from "@calcom/prisma/zod"; export const schemaEventTypeCustomInputBaseBodyParams = EventTypeCustomInput.omit({ id: true, - eventTypeId: true, -}).partial(); +}); export const schemaEventTypeCustomInputPublic = EventTypeCustomInput.omit({}); -const schemaEventTypeCustomInputRequiredParams = z.object({ - label: z.string(), - required: z.boolean(), - type: z.enum(["TEXT", "TEXTLONG", "NUMBER", "BOOL"]), - eventType: z.object({ - connect: z.object({ - id: z.number().optional(), - }), - // FIXME: Provide valid EventTypeModel schema here, but not sure how yet. - create: z.any(), - }), -}); +export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.strict(); -export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.merge( - schemaEventTypeCustomInputRequiredParams -); +export const schemaEventTypeCustomInputEditBodyParams = schemaEventTypeCustomInputBaseBodyParams + .partial() + .strict(); diff --git a/pages/api/custom-inputs/[id].ts b/pages/api/custom-inputs/[id].ts deleted file mode 100644 index 5b778714ad..0000000000 --- a/pages/api/custom-inputs/[id].ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -import { withMiddleware } from "@lib/helpers/withMiddleware"; -import type { EventTypeCustomInputResponse } from "@lib/types"; -import { - schemaEventTypeCustomInputBodyParams, - schemaEventTypeCustomInputPublic, -} from "@lib/validations/event-type-custom-input"; -import { - schemaQueryIdParseInt, - withValidQueryIdTransformParseInt, -} from "@lib/validations/shared/queryIdTransformParseInt"; - -/** - * @swagger - * /custom-inputs/{id}: - * get: - * summary: Find a eventTypeCustomInput - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the eventTypeCustomInput to get - * tags: - * - custom-inputs - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: EventType was not found - * patch: - * summary: Edit an existing eventTypeCustomInput - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the eventTypeCustomInput to edit - * tags: - * - custom-inputs - * responses: - * 201: - * description: OK, eventTypeCustomInput edited successfuly - * 400: - * description: Bad request. EventType body is invalid. - * 401: - * description: Authorization information is missing or invalid. - * delete: - * summary: Remove an existing eventTypeCustomInput - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: ID of the eventTypeCustomInput to delete - * tags: - * - custom-inputs - * responses: - * 201: - * description: OK, eventTypeCustomInput removed successfuly - * 400: - * description: Bad request. EventType id is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ -async function eventTypeById( - { method, query, body, userId, prisma }: NextApiRequest, - res: NextApiResponse -) { - const safeQuery = schemaQueryIdParseInt.safeParse(query); - const safeBody = schemaEventTypeCustomInputBodyParams.safeParse(body); - if (!safeQuery.success) { - res.status(400).json({ message: "Your query was invalid" }); - return; - } - const data = await prisma.eventType.findMany({ where: { userId } }); - const userEventTypes = data.map((eventType) => eventType.id); - const userEventTypeCustomInputs = await prisma.eventTypeCustomInput.findMany({ - where: { eventType: userEventTypes }, - }); - const userEventTypeCustomInputIds = userEventTypeCustomInputs.map( - (eventTypeCustomInput) => eventTypeCustomInput.id - ); - if (!userEventTypeCustomInputIds.includes(safeQuery.data.id)) - res.status(401).json({ message: "Unauthorized" }); - else { - switch (method) { - case "GET": - await prisma.eventTypeCustomInput - .findUnique({ where: { id: safeQuery.data.id } }) - .then((data) => schemaEventTypeCustomInputPublic.parse(data)) - .then((event_type_custom_input) => res.status(200).json({ event_type_custom_input })) - .catch((error: Error) => - res.status(404).json({ - message: `EventType with id: ${safeQuery.data.id} not found`, - error, - }) - ); - break; - - case "PATCH": - if (!safeBody.success) { - { - res.status(400).json({ message: "Invalid request body" }); - return; - } - } - await prisma.eventTypeCustomInput - .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) - .then((data) => schemaEventTypeCustomInputPublic.parse(data)) - .then((event_type_custom_input) => res.status(200).json({ event_type_custom_input })) - .catch((error: Error) => - res.status(404).json({ - message: `EventType with id: ${safeQuery.data.id} not found`, - error, - }) - ); - break; - - case "DELETE": - await prisma.eventTypeCustomInput - .delete({ - where: { id: safeQuery.data.id }, - }) - .then(() => - res.status(200).json({ - message: `CustomInputEventType with id: ${safeQuery.data.id} deleted`, - }) - ) - .catch((error: Error) => - res.status(404).json({ - message: `EventType 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(eventTypeById)); diff --git a/pages/api/custom-inputs/[id]/_auth-middleware.ts b/pages/api/custom-inputs/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..1f4aff75f5 --- /dev/null +++ b/pages/api/custom-inputs/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Check if the current user can access the event type of this input + const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({ + where: { id, eventType: { userId } }, + }); + if (!eventTypeCustomInput) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); +} + +export default authMiddleware; diff --git a/pages/api/custom-inputs/[id]/_delete.ts b/pages/api/custom-inputs/[id]/_delete.ts new file mode 100644 index 0000000000..aa1e09e3f1 --- /dev/null +++ b/pages/api/custom-inputs/[id]/_delete.ts @@ -0,0 +1,36 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * delete: + * summary: Remove an existing eventTypeCustomInput + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to delete + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput removed successfully + * 400: + * description: Bad request. EventType 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.eventTypeCustomInput.delete({ where: { id } }); + return { message: `CustomInputEventType with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/pages/api/custom-inputs/[id]/_get.ts b/pages/api/custom-inputs/[id]/_get.ts new file mode 100644 index 0000000000..b3b7ce8668 --- /dev/null +++ b/pages/api/custom-inputs/[id]/_get.ts @@ -0,0 +1,37 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeCustomInputPublic } from "@lib/validations/event-type-custom-input"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * get: + * summary: Find a eventTypeCustomInput + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to get + * tags: + * - custom-inputs + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: EventType was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = await prisma.eventTypeCustomInput.findUniqueOrThrow({ where: { id } }); + return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/pages/api/custom-inputs/[id]/_patch.ts b/pages/api/custom-inputs/[id]/_patch.ts new file mode 100644 index 0000000000..034d45f37b --- /dev/null +++ b/pages/api/custom-inputs/[id]/_patch.ts @@ -0,0 +1,41 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaEventTypeCustomInputEditBodyParams, + schemaEventTypeCustomInputPublic, +} from "@lib/validations/event-type-custom-input"; +import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * patch: + * summary: Edit an existing eventTypeCustomInput + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to edit + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput edited successfully + * 400: + * description: Bad request. EventType body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaEventTypeCustomInputEditBodyParams.parse(req.body); + const result = await prisma.eventTypeCustomInput.update({ where: { id }, data }); + return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(result) }; +} + +export default defaultResponder(patchHandler); diff --git a/pages/api/custom-inputs/[id]/index.ts b/pages/api/custom-inputs/[id]/index.ts new file mode 100644 index 0000000000..7d92ee2906 --- /dev/null +++ b/pages/api/custom-inputs/[id]/index.ts @@ -0,0 +1,18 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "@lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware("HTTP_GET_DELETE_PATCH")( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/pages/api/custom-inputs/_get.ts b/pages/api/custom-inputs/_get.ts new file mode 100644 index 0000000000..0a2d30d9d4 --- /dev/null +++ b/pages/api/custom-inputs/_get.ts @@ -0,0 +1,30 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeCustomInputPublic } from "@lib/validations/event-type-custom-input"; + +/** + * @swagger + * /custom-inputs: + * get: + * summary: Find all eventTypeCustomInputs + * tags: + * - custom-inputs + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No eventTypeCustomInputs were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.EventTypeCustomInputFindManyArgs = isAdmin ? {} : { where: { eventType: { userId } } }; + const data = await prisma.eventTypeCustomInput.findMany(args); + return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) }; +} + +export default defaultResponder(getHandler); diff --git a/pages/api/custom-inputs/_post.ts b/pages/api/custom-inputs/_post.ts new file mode 100644 index 0000000000..ea9da7da4f --- /dev/null +++ b/pages/api/custom-inputs/_post.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaEventTypeCustomInputBodyParams, + schemaEventTypeCustomInputPublic, +} from "@lib/validations/event-type-custom-input"; + +/** + * @swagger + * /custom-inputs: + * post: + * summary: Creates a new eventTypeCustomInput + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput created + * 400: + * description: Bad request. EventTypeCustomInput body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { eventTypeId, ...body } = schemaEventTypeCustomInputBodyParams.parse(req.body); + + if (!isAdmin) { + /* We check that the user has access to the event type he's trying to add a custom input to. */ + const eventType = await prisma.eventType.findFirst({ + where: { id: eventTypeId, userId }, + }); + if (!eventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); + } + + const data = await prisma.eventTypeCustomInput.create({ + data: { ...body, eventType: { connect: { id: eventTypeId } } }, + }); + + return { + event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data), + message: "EventTypeCustomInput created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/pages/api/custom-inputs/index.ts b/pages/api/custom-inputs/index.ts index 17500249aa..c07846423f 100644 --- a/pages/api/custom-inputs/index.ts +++ b/pages/api/custom-inputs/index.ts @@ -1,91 +1,10 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { defaultHandler } from "@calcom/lib/server"; import { withMiddleware } from "@lib/helpers/withMiddleware"; -import { EventTypeCustomInputResponse, EventTypeCustomInputsResponse } from "@lib/types"; -import { - schemaEventTypeCustomInputBodyParams, - schemaEventTypeCustomInputPublic, -} from "@lib/validations/event-type-custom-input"; -async function createOrlistAllEventTypeCustomInputs( - { userId, method, body, prisma }: NextApiRequest, - res: NextApiResponse -) { - const data = await prisma.eventType.findMany({ where: { userId } }); - const userEventTypes: number[] = data.map((eventType) => eventType.id); - if (method === "GET") { - /** - * @swagger - * /custom-inputs: - * get: - * summary: Find all eventTypeCustomInputs - * tags: - * - custom-inputs - * responses: - * 200: - * description: OK - * 401: - * description: Authorization information is missing or invalid. - * 404: - * description: No eventTypeCustomInputs were found - */ - const data = await prisma.eventTypeCustomInput.findMany({ where: { eventType: userEventTypes } }); - const event_type_custom_inputs = data.map((eventTypeCustomInput) => - schemaEventTypeCustomInputPublic.parse(eventTypeCustomInput) - ); - if (event_type_custom_inputs) res.status(200).json({ event_type_custom_inputs }); - else - (error: Error) => - res.status(404).json({ - message: "No EventTypeCustomInputs were found", - error, - }); - } else if (method === "POST") { - /** - * @swagger - * /custom-inputs: - * post: - * summary: Creates a new eventTypeCustomInput - * tags: - * - custom-inputs - * responses: - * 201: - * description: OK, eventTypeCustomInput created - * 400: - * description: Bad request. EventTypeCustomInput body is invalid. - * 401: - * description: Authorization information is missing or invalid. - */ - const safe = schemaEventTypeCustomInputBodyParams.safeParse(body); - if (!safe.success) { - res.status(400).json({ message: "Invalid request body" }); - return; - } - // Since we're supporting a create or connect relation on eventType, we need to treat them differently - // When using connect on event type, check if userId is the owner of the event - if (safe.data.eventType.connect && !userEventTypes.includes(safe.data.eventType.connect.id as number)) { - const data = await prisma.eventTypeCustomInput.create({ data: { ...safe.data } }); - const event_type_custom_input = schemaEventTypeCustomInputPublic.parse(data); - if (event_type_custom_input) - res - .status(201) - .json({ event_type_custom_input, message: "EventTypeCustomInput created successfully" }); - // When creating, no need - // FIXME: we might want to pass userId to the new created/linked eventType, though. - } else if (safe.data.eventType.create) { - const data = await prisma.eventTypeCustomInput.create({ data: { ...safe.data } }); - const event_type_custom_input = schemaEventTypeCustomInputPublic.parse(data); - if (event_type_custom_input) - res - .status(201) - .json({ event_type_custom_input, message: "EventTypeCustomInput created successfully" }); - } else - (error: Error) => - res.status(400).json({ - message: "Could not create new eventTypeCustomInput", - error, - }); - } else res.status(405).json({ message: `Method ${method} not allowed` }); -} - -export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllEventTypeCustomInputs); +export default withMiddleware("HTTP_GET_OR_POST")( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +);