diff --git a/lib/validations/event-type.ts b/lib/validations/event-type.ts index e8665a9e3b..25bb87491d 100644 --- a/lib/validations/event-type.ts +++ b/lib/validations/event-type.ts @@ -1,15 +1,92 @@ import { z } from "zod"; import { _EventTypeModel as EventType } from "@calcom/prisma/zod"; +import { eventTypeLocations, recurringEvent } from "@calcom/prisma/zod-utils"; -export const schemaEventTypeBaseBodyParams = EventType.omit({ id: true }).partial(); +import { jsonSchema } from "./shared/jsonSchema"; -const schemaEventTypeRequiredParams = z.object({ - title: z.string(), - slug: z.string(), - length: z.number(), -}); +export const schemaEventTypeBaseBodyParams = EventType.pick({ + title: true, + slug: true, + length: true, + hidden: true, + position: true, + userId: true, + teamId: true, + eventName: true, + timeZone: true, + periodType: true, + periodStartDate: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + requiresConfirmation: true, + disableGuests: true, + hideCalendarNotes: true, + minimumBookingNotice: true, + beforeEventBuffer: true, + afterEventBuffer: true, + schedulingType: true, + price: true, + currency: true, + slotInterval: true, + metadata: true, + successRedirectUrl: true, +}).partial(); -export const schemaEventTypeBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeRequiredParams); -// @NOTE: Removing locations and metadata properties before validation, add them later if required -export const schemaEventTypePublic = EventType.omit({ locations: true, metadata: true }); +const schemaEventTypeBaseParams = z + .object({ + title: z.string(), + slug: z.string(), + description: z.string().optional().nullable(), + length: z.number().int(), + locations: jsonSchema, + metadata: jsonSchema, + recurringEvent: jsonSchema, + }) + .strict(); + +export const schemaEventTypeCreateBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeBaseParams); + +const schemaEventTypeEditParams = z + .object({ + title: z.string().optional(), + slug: z.string().optional(), + length: z.number().int().optional(), + }) + .strict(); + +export const schemaEventTypeEditBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeEditParams); +export const schemaEventTypeReadPublic = EventType.pick({ + id: true, + title: true, + slug: true, + length: true, + locations: true, + hidden: true, + position: true, + userId: true, + teamId: true, + eventName: true, + timeZone: true, + periodType: true, + periodStartDate: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + requiresConfirmation: true, + recurringEvent: true, + disableGuests: true, + hideCalendarNotes: true, + minimumBookingNotice: true, + beforeEventBuffer: true, + afterEventBuffer: true, + schedulingType: true, + price: true, + currency: true, + slotInterval: true, + metadata: true, + successRedirectUrl: true, +}) + .merge(schemaEventTypeBaseParams) + .partial(); diff --git a/lib/validations/user.ts b/lib/validations/user.ts index b70d52f898..d78d8e23b2 100644 --- a/lib/validations/user.ts +++ b/lib/validations/user.ts @@ -4,6 +4,8 @@ import { _UserModel as User } from "@calcom/prisma/zod"; import { timeZone } from "@lib/validations/shared/timeZone"; +import { jsonSchema } from "./shared/jsonSchema"; + // @note: These are the ONLY values allowed as weekStart. So user don't introduce bad data. enum weekdays { MONDAY = "Monday", @@ -57,6 +59,7 @@ export const schemaUserBaseBodyParams = User.pick({ timeZone: true, weekStart: true, endTime: true, + metadata: true, bufferTime: true, theme: true, defaultScheduleId: true, @@ -89,6 +92,7 @@ const schemaUserEditParams = z.object({ .refine((id: number) => id > 0) .optional(), locale: z.nativeEnum(locales).optional(), + metadata: jsonSchema, }); // @note: These are the values that are editable via PATCH method on the user Model, @@ -104,6 +108,7 @@ export const schemaUserReadPublic = User.pick({ emailVerified: true, bio: true, avatar: true, + metadata: true, timeZone: true, weekStart: true, endTime: true, diff --git a/package.json b/package.json index 555cc3efaf..143065b933 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,4 @@ - { +{ "name": "@calcom/api", "version": "1.0.0", "description": "Public API for Cal.com", diff --git a/pages/api/booking-references/[id].ts b/pages/api/booking-references/[id].ts index d1c28b8d04..8041617bb3 100644 --- a/pages/api/booking-references/[id].ts +++ b/pages/api/booking-references/[id].ts @@ -132,7 +132,6 @@ export async function bookingReferenceById( console.log(safeBody.error); res.status(400).json({ message: "Invalid request body", error: safeBody.error }); return; - // throw new Error("Invalid request body"); } await prisma.bookingReference .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/booking-references/index.ts b/pages/api/booking-references/index.ts index 3386d5e42d..58e45d5c9e 100644 --- a/pages/api/booking-references/index.ts +++ b/pages/api/booking-references/index.ts @@ -97,17 +97,7 @@ async function createOrlistAllBookingReferences( if (!safe.success) { res.status(400).json({ message: "Bad request. BookingReference body is invalid", error: safe.error }); return; - // throw new Error("Invalid request body"); } - - 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 (!safe.data.bookingId) throw new Error("BookingReference: bookingId not found"); if (!userBookingIds.includes(safe.data.bookingId)) res.status(401).json({ message: "Unauthorized" }); else { diff --git a/pages/api/bookings/[id].ts b/pages/api/bookings/[id].ts index b094df73e9..b6a37122c1 100644 --- a/pages/api/bookings/[id].ts +++ b/pages/api/bookings/[id].ts @@ -106,7 +106,6 @@ export async function bookingById( console.log(safeBody.error); res.status(400).json({ message: "Bad request", error: safeBody.error }); return; - // throw new Error("Invalid request body"); } await prisma.booking .update({ diff --git a/pages/api/bookings/index.ts b/pages/api/bookings/index.ts index 00b92932a7..40f24283ee 100644 --- a/pages/api/bookings/index.ts +++ b/pages/api/bookings/index.ts @@ -76,7 +76,6 @@ async function createOrlistAllBookings( console.log(safe.error); res.status(400).json({ message: "Bad request. Booking body is invalid." }); return; - // throw new Error("Invalid request body"); } safe.data.userId = userId; const data = await prisma.booking.create({ data: { ...safe.data } }); diff --git a/pages/api/custom-inputs/[id].ts b/pages/api/custom-inputs/[id].ts index eb5493235f..8c20771a45 100644 --- a/pages/api/custom-inputs/[id].ts +++ b/pages/api/custom-inputs/[id].ts @@ -105,7 +105,10 @@ async function eventTypeById( case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.eventTypeCustomInput .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/custom-inputs/index.ts b/pages/api/custom-inputs/index.ts index c7d5f9df90..e1c2d484ed 100644 --- a/pages/api/custom-inputs/index.ts +++ b/pages/api/custom-inputs/index.ts @@ -59,7 +59,10 @@ async function createOrlistAllEventTypeCustomInputs( * description: Authorization information is missing or invalid. */ const safe = schemaEventTypeCustomInputBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request 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)) { diff --git a/pages/api/destination-calendars/[id].ts b/pages/api/destination-calendars/[id].ts index 60e8aae6b3..675f8c2e28 100644 --- a/pages/api/destination-calendars/[id].ts +++ b/pages/api/destination-calendars/[id].ts @@ -121,7 +121,10 @@ export async function destionationCalendarById( */ case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.destinationCalendar .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/destination-calendars/index.ts b/pages/api/destination-calendars/index.ts index 4006a67c72..bfd7df6d89 100644 --- a/pages/api/destination-calendars/index.ts +++ b/pages/api/destination-calendars/index.ts @@ -57,7 +57,10 @@ async function createOrlistAllDestinationCalendars( * description: Authorization information is missing or invalid. */ const safe = schemaDestinationCalendarCreateBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const data = await prisma.destinationCalendar.create({ data: { ...safe.data, userId } }); const destination_calendar = schemaDestinationCalendarReadPublic.parse(data); diff --git a/pages/api/event-references/[id].ts b/pages/api/event-references/[id].ts index 03ee39f49e..97bb53bc82 100644 --- a/pages/api/event-references/[id].ts +++ b/pages/api/event-references/[id].ts @@ -93,7 +93,10 @@ export async function dailyEventReferenceById( */ case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.dailyEventReference .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/event-references/index.ts b/pages/api/event-references/index.ts index e0b18a7919..48a438e321 100644 --- a/pages/api/event-references/index.ts +++ b/pages/api/event-references/index.ts @@ -64,7 +64,10 @@ async function createOrlistAllDailyEventReferences( * description: Authorization information is missing or invalid. */ const safe = schemaDailyEventReferenceCreateBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const data = await prisma.dailyEventReference.create({ data: safe.data }); const daily_event_reference = schemaDailyEventReferenceReadPublic.parse(data); diff --git a/pages/api/event-types/[id].ts b/pages/api/event-types/[id].ts index a6900b1d27..224a7c9995 100644 --- a/pages/api/event-types/[id].ts +++ b/pages/api/event-types/[id].ts @@ -4,7 +4,7 @@ import prisma from "@calcom/prisma"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import type { EventTypeResponse } from "@lib/types"; -import { schemaEventTypeBodyParams, schemaEventTypePublic } from "@lib/validations/event-type"; +import { schemaEventTypeEditBodyParams, schemaEventTypeReadPublic } from "@lib/validations/event-type"; import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt, @@ -52,7 +52,7 @@ export async function eventTypeById( case "GET": await prisma.eventType .findUnique({ where: { id: safeQuery.data.id } }) - .then((data) => schemaEventTypePublic.parse(data)) + .then((data) => schemaEventTypeReadPublic.parse(data)) .then((event_type) => res.status(200).json({ event_type })) .catch((error: Error) => res.status(404).json({ @@ -89,13 +89,16 @@ export async function eventTypeById( * description: Authorization information is missing or invalid. */ case "PATCH": - const safeBody = schemaEventTypeBodyParams.safeParse(body); + const safeBody = schemaEventTypeEditBodyParams.safeParse(body); if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.eventType .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) - .then((data) => schemaEventTypePublic.parse(data)) + .then((data) => schemaEventTypeReadPublic.parse(data)) .then((event_type) => res.status(200).json({ event_type })) .catch((error: Error) => res.status(404).json({ diff --git a/pages/api/event-types/index.ts b/pages/api/event-types/index.ts index dad45eec42..163fa501fb 100644 --- a/pages/api/event-types/index.ts +++ b/pages/api/event-types/index.ts @@ -4,7 +4,7 @@ import prisma from "@calcom/prisma"; import { withMiddleware } from "@lib/helpers/withMiddleware"; import { EventTypeResponse, EventTypesResponse } from "@lib/types"; -import { schemaEventTypeBodyParams, schemaEventTypePublic } from "@lib/validations/event-type"; +import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "@lib/validations/event-type"; async function createOrlistAllEventTypes( { method, body, userId }: NextApiRequest, @@ -16,6 +16,7 @@ async function createOrlistAllEventTypes( * /event-types: * get: * summary: Find all event types + * operationId: listEventTypes * tags: * - event-types * externalDocs: @@ -29,7 +30,7 @@ async function createOrlistAllEventTypes( * description: No event types were found */ const data = await prisma.eventType.findMany({ where: { userId } }); - const event_types = data.map((eventType) => schemaEventTypePublic.parse(eventType)); + const event_types = data.map((eventType) => schemaEventTypeReadPublic.parse(eventType)); if (event_types) res.status(200).json({ event_types }); else (error: Error) => @@ -43,6 +44,32 @@ async function createOrlistAllEventTypes( * /event-types: * post: * summary: Creates a new event type + * operationId: addEventType + * requestBody: + * description: Create a new event-type related to your user or team + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - title + * - slug + * - length + * - metadata + * properties: + * length: + * type: number + * example: 30 + * metadata: + * type: object + * example: {"smartContractAddress": "0x1234567890123456789012345678901234567890"} + * title: + * type: string + * example: My Event + * slug: + * type: string + * example: my-event * tags: * - event-types * externalDocs: @@ -55,11 +82,14 @@ async function createOrlistAllEventTypes( * 401: * description: Authorization information is missing or invalid. */ - const safe = schemaEventTypeBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + const safe = schemaEventTypeCreateBodyParams.safeParse(body); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body", error: safe.error }); + return; + } const data = await prisma.eventType.create({ data: { ...safe.data, userId } }); - const event_type = schemaEventTypePublic.parse(data); + const event_type = schemaEventTypeReadPublic.parse(data); if (data) res.status(201).json({ event_type, message: "EventType created successfully" }); else diff --git a/pages/api/hooks/[id].ts b/pages/api/hooks/[id].ts index 59ae3e3516..cc017d8e8f 100644 --- a/pages/api/hooks/[id].ts +++ b/pages/api/hooks/[id].ts @@ -85,7 +85,10 @@ export async function WebhookById( case "PATCH": const safeBody = schemaWebhookEditBodyParams.safeParse(body); if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.webhook .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/hooks/index.ts b/pages/api/hooks/index.ts index 9ce8615ea7..ccda5dcd76 100644 --- a/pages/api/hooks/index.ts +++ b/pages/api/hooks/index.ts @@ -56,7 +56,10 @@ async function createOrlistAllWebhooks( * description: Authorization information is missing or invalid. */ const safe = schemaWebhookCreateBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const data = await prisma.webhook.create({ data: { ...safe.data, userId } }); const webhook = schemaWebhookReadPublic.parse(data); diff --git a/pages/api/memberships/[id].ts b/pages/api/memberships/[id].ts index 0681a629a9..1b9ceaada8 100644 --- a/pages/api/memberships/[id].ts +++ b/pages/api/memberships/[id].ts @@ -97,7 +97,10 @@ export async function membershipById( case "PATCH": const safeBody = schemaMembershipBodyParams.safeParse(body); if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.membership .update({ diff --git a/pages/api/memberships/index.ts b/pages/api/memberships/index.ts index 2000ed84e3..efd3fb278c 100644 --- a/pages/api/memberships/index.ts +++ b/pages/api/memberships/index.ts @@ -52,7 +52,10 @@ async function createOrlistAllMemberships( * description: Authorization information is missing or invalid. */ const safe = schemaMembershipBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const data = await prisma.membership.create({ data: { ...safe.data, userId } }); const membership = schemaMembershipPublic.parse(data); diff --git a/pages/api/schedules/[id].ts b/pages/api/schedules/[id].ts index 5b90027ac3..a6e65a87a3 100644 --- a/pages/api/schedules/[id].ts +++ b/pages/api/schedules/[id].ts @@ -83,7 +83,10 @@ export async function scheduleById( */ case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.schedule .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/schedules/index.ts b/pages/api/schedules/index.ts index ac002345a3..4421844047 100644 --- a/pages/api/schedules/index.ts +++ b/pages/api/schedules/index.ts @@ -54,7 +54,10 @@ async function createOrlistAllSchedules( * description: Authorization information is missing or invalid. */ const safe = schemaScheduleBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const data = await prisma.schedule.create({ data: { ...safe.data, userId } }); const schedule = schemaSchedulePublic.parse(data); diff --git a/pages/api/selected-calendars/[id].ts b/pages/api/selected-calendars/[id].ts index 3915332412..5081da3d87 100644 --- a/pages/api/selected-calendars/[id].ts +++ b/pages/api/selected-calendars/[id].ts @@ -115,7 +115,10 @@ export async function selectedCalendarById( */ case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.selectedCalendar .update({ diff --git a/pages/api/selected-calendars/index.ts b/pages/api/selected-calendars/index.ts index e7ccd9d101..f0021f9f0e 100644 --- a/pages/api/selected-calendars/index.ts +++ b/pages/api/selected-calendars/index.ts @@ -70,7 +70,10 @@ async function createOrlistAllSelectedCalendars( * description: Authorization information is missing or invalid. */ const safe = schemaSelectedCalendarBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } // Create new selectedCalendar connecting it to current userId const data = await prisma.selectedCalendar.create({ data: { ...safe.data, user: { connect: { id: userId } } }, diff --git a/pages/api/teams/[id].ts b/pages/api/teams/[id].ts index adbc5e0c12..4c8262efdc 100644 --- a/pages/api/teams/[id].ts +++ b/pages/api/teams/[id].ts @@ -102,7 +102,10 @@ export async function teamById( case "PATCH": if (!safeBody.success) { - throw new Error("Invalid request body"); + { + res.status(400).json({ message: "Invalid request body" }); + return; + } } await prisma.team .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) diff --git a/pages/api/teams/index.ts b/pages/api/teams/index.ts index 5ab45bc21d..e5f870cef6 100644 --- a/pages/api/teams/index.ts +++ b/pages/api/teams/index.ts @@ -58,7 +58,10 @@ async function createOrlistAllTeams( * description: Authorization information is missing or invalid. */ const safe = schemaTeamBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const team = await prisma.team.create({ data: safe.data }); // We're also creating the relation membership of team ownership in this call. const membership = await prisma.membership diff --git a/pages/api/users/[id].ts b/pages/api/users/[id].ts index ffaabcbf4f..b73abaf520 100644 --- a/pages/api/users/[id].ts +++ b/pages/api/users/[id].ts @@ -104,7 +104,7 @@ export async function userById( const safeBody = schemaUserEditBodyParams.safeParse(body); if (!safeBody.success) { res.status(400).json({ message: "Bad request", error: safeBody.error }); - // throw new Error("Invalid request body"); + return; } const userSchedules = await prisma.schedule.findMany({ diff --git a/templates/endpoints/get_all_and_post.ts b/templates/endpoints/get_all_and_post.ts index ba35a7525b..d1cc4ea665 100644 --- a/templates/endpoints/get_all_and_post.ts +++ b/templates/endpoints/get_all_and_post.ts @@ -54,7 +54,10 @@ async function createOrlistAllPayments( * description: Authorization information is missing or invalid. */ const safe = schemaPaymentBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const payment = await prisma.payment.create({ data: safe.data }); const data = schemaPaymentPublic.parse(payment); diff --git a/templates/endpoints/post.ts b/templates/endpoints/post.ts index 61cff72a15..2b91522c62 100644 --- a/templates/endpoints/post.ts +++ b/templates/endpoints/post.ts @@ -31,7 +31,10 @@ import { schemaResourceBodyParams, schemaResourcePublic, withValidResource } fro */ async function createResource({ body }: NextApiRequest, res: NextApiResponse) { const safe = schemaResourceBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } const resource = await prisma.resource.create({ data: safe.data }); const data = schemaResourcePublic.parse(resource);