diff --git a/lib/helpers/withCost.ts b/lib/helpers/withCost.ts new file mode 100644 index 0000000000..55f8e06ee5 --- /dev/null +++ b/lib/helpers/withCost.ts @@ -0,0 +1,25 @@ +// Make a middleware that adds a cost to running the request +// by calling stripeSubscription addCost() * pricePerBooking +// Initially to test out 0,5 cent per booking via API call +// withCost(5)(endpoint) +// Should add a charge of 0.5 cent per booking to the subscription of the user making the request +// Should be called in the middleware of the e +// +import { NextMiddleware } from "next-api-middleware"; + +export const withCost = (priceInCents: number): NextMiddleware => { + return async function (req, res, next) { + console.log(req.headers); + if ( + priceInCents > 0 + // && stripeCustomerId && stripeSubscriptionId + ) { + console.log(priceInCents); + // if (req.method === allowedHttpMethod || req.method == "OPTIONS") { + await next(); + } else { + res.status(405).json({ message: `We weren't able to process the payment for this transaction` }); + res.end(); + } + }; +}; diff --git a/lib/helpers/withMiddleware.ts b/lib/helpers/withMiddleware.ts index c410882081..e15c388d51 100644 --- a/lib/helpers/withMiddleware.ts +++ b/lib/helpers/withMiddleware.ts @@ -2,7 +2,7 @@ import { label } from "next-api-middleware"; import { addRequestId } from "./addRequestid"; import { captureErrors } from "./captureErrors"; -import { HTTP_POST, HTTP_DELETE, HTTP_PATCH, HTTP_GET, httpMethod } from "./httpMethods"; +import { HTTP_POST, HTTP_DELETE, HTTP_PATCH, HTTP_GET } from "./httpMethods"; import { verifyApiKey } from "./verifyApiKey"; const withMiddleware = label( @@ -14,9 +14,8 @@ const withMiddleware = label( addRequestId, verifyApiKey, sentry: captureErrors, - httpMethod: httpMethod("GET" || "DELETE" || "PATCH" || "POST"), }, - ["sentry", "verifyApiKey", "httpMethod", "addRequestId"] // <-- Provide a list of middleware to call automatically + ["sentry", "verifyApiKey", "addRequestId"] // <-- Provide a list of middleware to call automatically ); export { withMiddleware }; diff --git a/lib/validations/apiKey.ts b/lib/validations/apiKey.ts index 2d93b31e09..e12a50cefa 100644 --- a/lib/validations/apiKey.ts +++ b/lib/validations/apiKey.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _ApiKeyModel as ApiKey } from "@calcom/prisma/zod"; -export const schemaApiKeyBodyParams = ApiKey.omit({ id: true, userId: true, createdAt: true }); +export const schemaApiKeyBodyParams = ApiKey.omit({ id: true, userId: true, createdAt: true }).partial(); export const schemaApiKeyPublic = ApiKey.omit({ id: true, diff --git a/lib/validations/attendee.ts b/lib/validations/attendee.ts index bd55fb9361..5e62be3580 100644 --- a/lib/validations/attendee.ts +++ b/lib/validations/attendee.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _AttendeeModel as Attendee } from "@calcom/prisma/zod"; -export const schemaAttendeeBodyParams = Attendee.omit({ id: true }); +export const schemaAttendeeBodyParams = Attendee.omit({ id: true }).partial(); export const schemaAttendeePublic = Attendee.omit({}); diff --git a/lib/validations/booking.ts b/lib/validations/booking.ts index e7528764ab..df5293e053 100644 --- a/lib/validations/booking.ts +++ b/lib/validations/booking.ts @@ -1,8 +1,20 @@ import { withValidation } from "next-validations"; +import { z } from "zod"; import { _BookingModel as Booking } from "@calcom/prisma/zod"; -export const schemaBookingBodyParams = Booking.omit({ id: true }); +// Here we remove any parameters that are not needed for the API with omit. +const schemaBookingBaseBodyParams = Booking.omit({ id: true }).partial(); +// Here we redeclare the required ones after removing the ones we don't need. +// and making the rest optional with .partial() +const schemaBookingRequiredParams = z.object({ + uid: z.string(), + title: z.string(), + startTime: z.date(), + endTime: z.date(), +}); + +export const schemaBookingBodyParams = schemaBookingBaseBodyParams.merge(schemaBookingRequiredParams); export const schemaBookingPublic = Booking.omit({}); diff --git a/lib/validations/schedule.ts b/lib/validations/schedule.ts index ad6d403b00..dca4a77345 100644 --- a/lib/validations/schedule.ts +++ b/lib/validations/schedule.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _ScheduleModel as Schedule } from "@calcom/prisma/zod"; -export const schemaScheduleBodyParams = Schedule.omit({ id: true }); +export const schemaScheduleBodyParams = Schedule.omit({ id: true }).partial(); export const schemaSchedulePublic = Schedule.omit({}); diff --git a/lib/validations/selected-calendar.ts b/lib/validations/selected-calendar.ts index 2c8d871d52..f1bb0483ae 100644 --- a/lib/validations/selected-calendar.ts +++ b/lib/validations/selected-calendar.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _SelectedCalendarModel as SelectedCalendar } from "@calcom/prisma/zod"; -export const schemaSelectedCalendarBodyParams = SelectedCalendar.omit({}); +export const schemaSelectedCalendarBodyParams = SelectedCalendar.omit({}).partial(); export const schemaSelectedCalendarPublic = SelectedCalendar.omit({ userId: true }); diff --git a/lib/validations/team.ts b/lib/validations/team.ts index 20b79ac1c4..010d73282a 100644 --- a/lib/validations/team.ts +++ b/lib/validations/team.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _TeamModel as Team } from "@calcom/prisma/zod"; -export const schemaTeamBodyParams = Team.omit({ id: true }); +export const schemaTeamBodyParams = Team.omit({ id: true }).partial(); export const schemaTeamPublic = Team.omit({}); diff --git a/lib/validations/user.ts b/lib/validations/user.ts index 2058a20c2e..a626456145 100644 --- a/lib/validations/user.ts +++ b/lib/validations/user.ts @@ -8,7 +8,7 @@ export const schemaUserBodyParams = User.omit({ password: true, twoFactorEnabled: true, twoFactorSecret: true, -}); +}).partial(); export const schemaUserPublic = User.omit({ identityProvider: true, diff --git a/lib/validations/webhook.ts b/lib/validations/webhook.ts index 198f4797a8..746443213d 100644 --- a/lib/validations/webhook.ts +++ b/lib/validations/webhook.ts @@ -2,7 +2,7 @@ import { withValidation } from "next-validations"; import { _WebhookModel as Webhook } from "@calcom/prisma/zod"; -export const schemaWebhookBodyParams = Webhook.omit({ id: true }); +export const schemaWebhookBodyParams = Webhook.omit({ id: true }).partial(); export const schemaWebhookPublic = Webhook.omit({}); diff --git a/package.json b/package.json index 8f496b55e7..8507464bf3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "next-swagger-doc": "^0.2.1", "next-transpile-modules": "^9.0.0", "next-validations": "^0.1.11", - "typescript": "^4.6.3", - "zod": "^3.14.2" + "typescript": "^4.6.3" } } diff --git a/pages/api/bookings/new.ts b/pages/api/bookings/new.ts index 50dc9ffbf2..fcc5f89efc 100644 --- a/pages/api/bookings/new.ts +++ b/pages/api/bookings/new.ts @@ -1,30 +1,49 @@ import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "@calcom/prisma"; -import { Booking } from "@calcom/prisma/client"; -import { schemaBooking, withValidBooking } from "@lib/validations/booking"; +import { withCost } from "@lib/helpers/withCost"; +import { withMiddleware } from "@lib/helpers/withMiddleware"; +import type { BookingResponse } from "@lib/types"; +import { schemaBookingBodyParams, schemaBookingPublic, withValidBooking } from "@lib/validations/booking"; -type ResponseData = { - data?: Booking; - message?: string; - error?: string; -}; +/** + * @swagger + * /api/bookings/new: + * post: + * summary: Creates a new booking + * requestBody: + * description: Optional description in *Markdown* + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Booking' + * tags: + * - bookings + * responses: + * 201: + * description: OK, booking created + * model: Booking + * 400: + * description: Bad request. Booking body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function createBooking(req: NextApiRequest, res: NextApiResponse) { + const safe = schemaBookingBodyParams.safeParse(req.body); + if (!safe.success) throw new Error("Invalid request body", safe.error); -async function createBooking(req: NextApiRequest, res: NextApiResponse) { - const { body, method } = req; - if (method === "POST") { - const safe = schemaBooking.safeParse(body); - if (safe.success && safe.data) { - await prisma.booking - .create({ data: safe.data }) - .then((booking) => res.status(201).json({ data: booking })) - .catch((error) => res.status(400).json({ message: "Could not create booking type", error: error })); - } - } else { - // Reject any other HTTP method than POST - res.status(405).json({ error: "Only POST Method allowed" }); - } + const booking = await prisma.booking.create({ data: safe.data }); + const data = schemaBookingPublic.parse(booking); + + if (data) res.status(201).json({ data, message: "Booking created successfully" }); + else + (error: Error) => + res.status(400).json({ + message: "Could not create new booking", + error, + }); } -export default withValidBooking(createBooking); +export default withMiddleware(withCost(5), "HTTP_POST")(withValidBooking(createBooking)); diff --git a/pages/api/teams/[id]/index.ts b/pages/api/teams/[id]/index.ts index 0dedf16159..218f296308 100644 --- a/pages/api/teams/[id]/index.ts +++ b/pages/api/teams/[id]/index.ts @@ -12,9 +12,16 @@ import { schemaTeamPublic } from "@lib/validations/team"; /** * @swagger - * /api/teams/:id: + * /api/teams/{id}: * get: * summary: find team by ID + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: Numeric ID of the team to get * tags: * - teams * responses: diff --git a/pages/api/users/new.ts b/pages/api/users/new.ts index 70e6497b9b..52a25015b8 100644 --- a/pages/api/users/new.ts +++ b/pages/api/users/new.ts @@ -12,12 +12,12 @@ import { schemaUserBodyParams, schemaUserPublic, withValidUser } from "@lib/vali * post: * summary: Creates a new user * requestBody: - description: Optional description in *Markdown* - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/User' + * description: Optional description in *Markdown* + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/User' * tags: * - users * responses: