From 713f53acc4843ac7195703ba05c2b3897334faad Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Thu, 24 Mar 2022 21:25:35 +0100 Subject: [PATCH] feat: add next-validations and zod validations --- package.json | 1 + pages/api/event-types/[id].ts | 50 +++++++++++---- pages/api/event-types/index.ts | 13 ++-- pages/api/event-types/new.ts | 107 +++++++++++---------------------- pages/api/users/[id].ts | 6 +- pages/api/users/valid.ts | 19 ++++++ tsconfig.json | 16 +++-- 7 files changed, 114 insertions(+), 98 deletions(-) create mode 100644 pages/api/users/valid.ts diff --git a/package.json b/package.json index 1b9504d975..135808bdf2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "next": "^12.1.0", + "next-validations": "^0.1.11", "zod": "^3.14.2" } } diff --git a/pages/api/event-types/[id].ts b/pages/api/event-types/[id].ts index 508a917572..112f64937b 100644 --- a/pages/api/event-types/[id].ts +++ b/pages/api/event-types/[id].ts @@ -1,19 +1,47 @@ +import { PrismaClient, EventType } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; +import { withValidation } from "next-validations"; +import { z } from "zod"; -import prisma from "@calcom/prisma"; -import { EventType } from "@calcom/prisma/client"; +const prisma = new PrismaClient(); -type Data = { - event?: EventType; +const schema = z + .object({ + id: z + .string() + .regex(/^\d+$/) + .transform((id) => parseInt(id)), + }) + .strict(); + +const validate = withValidation({ + schema, + type: "Zod", + mode: "query", +}); + +type ResponseData = { + data?: EventType; error?: any; }; -export default async function eventType(req: NextApiRequest, res: NextApiResponse) { - try { - const event = await prisma.eventType.findUnique({ where: { id: Number(req.query.id) } }); - res.status(200).json({ event }); - } catch (error) { - console.log(error); - res.status(400).json({ error: error }); +export async function eventType(req: NextApiRequest, res: NextApiResponse) { + const { query, method } = req; + if (method === "GET") { + const safe = await schema.safeParse(query); + if (safe.success) { + try { + const event = await prisma.eventType.findUnique({ where: { id: safe.data.id } }); + res.status(200).json({ data: event }); + } catch (error) { + console.log(error); + res.status(400).json({ error: error }); + } + } + } else { + // Reject any other HTTP method than POST + res.status(405).json({ error: "Only GET Method allowed" }); } } + +export default validate(eventType); diff --git a/pages/api/event-types/index.ts b/pages/api/event-types/index.ts index edcd6d6270..49c69583c8 100644 --- a/pages/api/event-types/index.ts +++ b/pages/api/event-types/index.ts @@ -1,17 +1,16 @@ +import { PrismaClient, EventType } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; -import prisma from "@calcom/prisma"; -import { EventType } from "@calcom/prisma/client"; - -type Data = { - events?: EventType[]; +const prisma = new PrismaClient(); +type ResponseData = { + data?: EventType[]; error?: any; }; -export default async function eventType(req: NextApiRequest, res: NextApiResponse) { +export default async function eventType(req: NextApiRequest, res: NextApiResponse) { try { const eventTypes = await prisma.eventType.findMany({ where: { id: Number(req.query.eventTypeId) } }); - res.status(200).json({ events: { ...eventTypes } }); + res.status(200).json({ data: { ...eventTypes } }); } catch (error) { console.log(error); // FIXME: Add zod for validation/error handling diff --git a/pages/api/event-types/new.ts b/pages/api/event-types/new.ts index 529d0151c3..a9fc7d770e 100644 --- a/pages/api/event-types/new.ts +++ b/pages/api/event-types/new.ts @@ -1,86 +1,49 @@ +import { PrismaClient, EventType } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; +import { withValidation } from "next-validations"; +import { z } from "zod"; -import prisma from "@calcom/prisma"; -import { EventType } from "@calcom/prisma/client"; +const prisma = new PrismaClient(); + +const schema = z + .object({ + title: z.string().min(3), + slug: z.string().min(3), + length: z.number().min(1).max(1440), // max is a full day. + description: z.string().min(3).optional(), + }) + .strict(); // Adding strict so that we can disallow passing in extra fields + +const validate = withValidation({ + schema, + type: "Zod", + mode: "body", +}); interface Body { - userId: string; - - newOrganization: { - name: string; - users: string[]; - }; + title: string; + slug: string; + length: number; } type Data = { - event?: EventType; + data?: EventType; error?: string; }; -export default async function createEventLink(req: NextApiRequest, res: NextApiResponse) { - const { - body: { - title, - slug, - description, - position, - locations, - hidden, - teamId, - eventName, - timeZone, - periodType, - periodStartDate, - periodEndDate, - periodDays, - periodCountCalendarDays, - requiresConfirmation, - disableGuests, - minimumBookingNotice, - beforeEventBuffer, - afterEventBuffer, - schedulingType, - price, - currency, - slotInterval, - metadata, - length, - }, - method, - } = req; +type NextApiRequestWithBody = NextApiRequest & { + body: Body; +}; + +async function createEventType(req: NextApiRequestWithBody, res: NextApiResponse) { + const { body, method }: { body: Body; method?: string } = req; if (method === "POST") { - // Process a POST request - const newEvent = await prisma.eventType.create({ - data: { - title: `${title}`, - slug: `${slug}`, - length: Number(length), - // description: description as string, - // position: Number(position), - // locations: locations, - // hidden: Boolean(hidden) as boolean, - // teamId: Number.isInteger(teamId) ? Number(teamId) : null, - // eventName: eventName, - // timeZone: timeZone, - // periodType: periodType, - // periodStartDate: periodStartDate, - // periodEndDate: periodEndDate, - // periodDays: periodDays, - // periodCountCalendarDays: periodCountCalendarDays, - // requiresConfirmation: requiresConfirmation, - // disableGuests: disableGuests, - // minimumBookingNotice: minimumBookingNotice, - // beforeEventBuffer: beforeEventBuffer, - // afterEventBuffer: afterEventBuffer, - // schedulingType: schedulingType, - // price: price, - // currency: currency, - // slotInterval: slotInterval, - // metadata: metadata, - }, - }); - res.status(201).json({ event: newEvent }); + schema.safeParse(body); + const newEvent = await prisma.eventType.create({ data: body }); + res.status(201).json({ data: newEvent }); } else { - // Handle any other HTTP method + // Reject any other HTTP method than POST res.status(405).json({ error: "Only POST Method allowed" }); } } + +export default validate(createEventType); diff --git a/pages/api/users/[id].ts b/pages/api/users/[id].ts index 07618f8dc4..9a9cb8fc3f 100644 --- a/pages/api/users/[id].ts +++ b/pages/api/users/[id].ts @@ -1,9 +1,7 @@ -import { PrismaClient } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; -// This local import doesn't work -// import { PrismaClient } from "@calcom/prisma"; -const prisma = new PrismaClient(); +import prisma from "@calcom/prisma"; +import { User } from "@calcom/prisma/client"; type Data = { message: string; diff --git a/pages/api/users/valid.ts b/pages/api/users/valid.ts new file mode 100644 index 0000000000..81a4b3c94a --- /dev/null +++ b/pages/api/users/valid.ts @@ -0,0 +1,19 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { withValidation } from "next-validations"; +import { z } from "zod"; + +const schema = z.object({ + username: z.string().min(6), +}); + +const validate = withValidation({ + schema, + type: "Zod", + mode: "body", +}); + +const handler = (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).json(req.body); +}; + +export default validate(handler); diff --git a/tsconfig.json b/tsconfig.json index bc4d4d4777..e77f9df418 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,12 +4,20 @@ "node_modules" ], "compilerOptions": { - "lib": ["ES2015"], - "module": "CommonJS", - "outDir": "./dist", - "rootDir": "./", "strictNullChecks": true, "baseUrl": "./", + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "noEmit": true, + "incremental": true, + "module": "esnext", + "resolveJsonModule": true, + "jsx": "preserve" }, "include": [ "./pages/api/*.ts"