feat: inherit all validations from auto-generated zod

pull/9078/head
Agusti Fernandez Pardo 2022-03-30 17:37:51 +02:00
parent 064c39b7f6
commit 9aa4b0e30d
19 changed files with 372 additions and 224 deletions

View File

@ -1,4 +1,20 @@
import { User, ApiKey, Team, SelectedCalendar } from "@calcom/prisma/client";
import {
User,
ApiKey,
Team,
SelectedCalendar,
EventType,
Attendee,
Availability,
BookingReference,
Booking,
DailyEventReference,
Webhook,
DestinationCalendar,
Membership,
Payment,
Schedule,
} from "@calcom/prisma/client";
// Base response, used for all responses
export type BaseResponse = {
@ -21,7 +37,7 @@ export type ApiKeysResponse = BaseResponse & {
data?: Partial<ApiKey>[];
};
// API Key
// Team
export type TeamResponse = BaseResponse & {
data?: Partial<Team>;
};
@ -29,10 +45,106 @@ export type TeamsResponse = BaseResponse & {
data?: Partial<Team>[];
};
// API Key
// SelectedCalendar
export type SelectedCalendarResponse = BaseResponse & {
data?: Partial<SelectedCalendar>;
};
export type SelectedCalendarsResponse = BaseResponse & {
data?: Partial<SelectedCalendar>[];
};
// Attendee
export type AttendeeResponse = BaseResponse & {
data?: Partial<Attendee>;
};
export type AttendeesResponse = BaseResponse & {
data?: Partial<Attendee>[];
};
// Availability
export type AvailabilityResponse = BaseResponse & {
data?: Partial<Availability>;
};
export type AvailabilitysResponse = BaseResponse & {
data?: Partial<Availability>[];
};
// BookingReference
export type BookingReferenceResponse = BaseResponse & {
data?: Partial<BookingReference>;
};
export type BookingReferencesResponse = BaseResponse & {
data?: Partial<BookingReference>[];
};
// Booking
export type BookingResponse = BaseResponse & {
data?: Partial<Booking>;
};
export type BookingsResponse = BaseResponse & {
data?: Partial<Booking>[];
};
// Credential
export type CredentialResponse = BaseResponse & {
data?: Partial<Credential>;
};
export type CredentialsResponse = BaseResponse & {
data?: Partial<Credential>[];
};
// DailyEventReference
export type DailyEventReferenceResponse = BaseResponse & {
data?: Partial<DailyEventReference>;
};
export type DailyEventReferencesResponse = BaseResponse & {
data?: Partial<DailyEventReference>[];
};
// DestinationCalendar
export type DestinationCalendarResponse = BaseResponse & {
data?: Partial<DestinationCalendar>;
};
export type DestinationCalendarsResponse = BaseResponse & {
data?: Partial<DestinationCalendar>[];
};
// Membership
export type MembershipResponse = BaseResponse & {
data?: Partial<Membership>;
};
export type MembershipsResponse = BaseResponse & {
data?: Partial<Membership>[];
};
// EventType
export type EventTypeResponse = BaseResponse & {
data?: Partial<EventType>;
};
export type EventTypesResponse = BaseResponse & {
data?: Partial<EventType>[];
};
// Payment
export type PaymentResponse = BaseResponse & {
data?: Partial<Payment>;
};
export type PaymentsResponse = BaseResponse & {
data?: Partial<Payment>[];
};
// Schedule
export type ScheduleResponse = BaseResponse & {
data?: Partial<Schedule>;
};
export type SchedulesResponse = BaseResponse & {
data?: Partial<Schedule>[];
};
// Webhook
export type WebhookResponse = BaseResponse & {
data?: Partial<Webhook>;
};
export type WebhooksResponse = BaseResponse & {
data?: Partial<Webhook>[];
};

View File

@ -1,20 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaAttendee = z
.object({
id: z.number(),
email: z.string().min(3),
name: z.string().min(3).email(),
timeZone: z.string().default("Europe/London"),
locale: z.string().optional(),
bookingId: z.number(),
})
.strict();
const withValidAttendee = withValidation({
schema: schemaAttendee,
import { _AttendeeModel as Attendee } from "@calcom/prisma/zod";
export const schemaAttendeeBodyParams = Attendee.omit({ id: true });
export const schemaAttendeePublic = Attendee.omit({});
export const withValidAttendee = withValidation({
schema: schemaAttendeeBodyParams,
type: "Zod",
mode: "body",
});
export { schemaAttendee, withValidAttendee };

View File

@ -1,23 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaAvailability = z
.object({
id: z.number(),
userId: z.number(),
eventTypeId: z.number(),
scheduleId: z.number(),
import { _AvailabilityModel as Availability } from "@calcom/prisma/zod";
days: z.array(z.number()),
date: z.date().or(z.string()),
startTime: z.string(),
endTime: z.string(),
})
.strict();
const withValidAvailability = withValidation({
schema: schemaAvailability,
export const schemaAvailabilityBodyParams = Availability.omit({ id: true });
export const schemaAvailabilityPublic = Availability.omit({});
export const withValidAvailability = withValidation({
schema: schemaAvailabilityBodyParams,
type: "Zod",
mode: "body",
});
export { schemaAvailability, withValidAvailability };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaBookingReference = z.object({}).strict();
const withValidBookingReference = withValidation({
schema: schemaBookingReference,
import { _BookingReferenceModel as BookingReference } from "@calcom/prisma/zod";
export const schemaBookingReferenceBodyParams = BookingReference.omit({ id: true });
export const schemaBookingReferencePublic = BookingReference.omit({});
export const withValidBookingReference = withValidation({
schema: schemaBookingReferenceBodyParams,
type: "Zod",
mode: "body",
});
export { schemaBookingReference, withValidBookingReference };

View File

@ -1,25 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaBooking = z
.object({
uid: z.string().min(3),
title: z.string().min(3),
description: z.string().min(3).optional(),
startTime: z.date().or(z.string()),
endTime: z.date(),
location: z.string().min(3).optional(),
createdAt: z.date().or(z.string()),
updatedAt: z.date().or(z.string()),
confirmed: z.boolean().default(true),
rejected: z.boolean().default(false),
paid: z.boolean().default(false),
})
.strict();
const withValidBooking = withValidation({
schema: schemaBooking,
import { _BookingModel as Booking } from "@calcom/prisma/zod";
export const schemaBookingBodyParams = Booking.omit({ id: true });
export const schemaBookingPublic = Booking.omit({});
export const withValidBooking = withValidation({
schema: schemaBookingBodyParams,
type: "Zod",
mode: "body",
});
export { schemaBooking, withValidBooking };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaCredential = z.object({}).strict();
const withValidCredential = withValidation({
schema: schemaCredential,
import { _CredentialModel as Credential } from "@calcom/prisma/zod";
export const schemaCredentialBodyParams = Credential.omit({ id: true });
export const schemaCredentialPublic = Credential.omit({});
export const withValidCredential = withValidation({
schema: schemaCredentialBodyParams,
type: "Zod",
mode: "body",
});
export { schemaCredential, withValidCredential };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaDailyEventReference = z.object({}).strict();
const withValidDailyEventReference = withValidation({
schema: schemaDailyEventReference,
import { _DailyEventReferenceModel as DailyEventReference } from "@calcom/prisma/zod";
export const schemaDailyEventReferenceBodyParams = DailyEventReference.omit({ id: true });
export const schemaDailyEventReferencePublic = DailyEventReference.omit({});
export const withValidDailyEventReference = withValidation({
schema: schemaDailyEventReferenceBodyParams,
type: "Zod",
mode: "body",
});
export { schemaDailyEventReference, withValidDailyEventReference };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaDestinationCalendar = z.object({}).strict();
const withValidDestinationCalendar = withValidation({
schema: schemaDestinationCalendar,
import { _DestinationCalendarModel as DestinationCalendar } from "@calcom/prisma/zod";
export const schemaDestinationCalendarBodyParams = DestinationCalendar.omit({ id: true });
export const schemaDestinationCalendarPublic = DestinationCalendar.omit({});
export const withValidDestinationCalendar = withValidation({
schema: schemaDestinationCalendarBodyParams,
type: "Zod",
mode: "body",
});
export { schemaDestinationCalendar, withValidDestinationCalendar };

View File

@ -1,18 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaEventType = 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();
const withValidEventType = withValidation({
schema: schemaEventType,
import { _EventTypeModel as EventType } from "@calcom/prisma/zod";
export const schemaEventTypeBodyParams = EventType.omit({ id: true });
export const schemaEventTypePublic = EventType.omit({});
export const withValidEventType = withValidation({
schema: schemaEventTypeBodyParams,
type: "Zod",
mode: "body",
});
export { schemaEventType, withValidEventType };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaMembership = z.object({}).strict();
const withValidMembership = withValidation({
schema: schemaMembership,
import { _MembershipModel as Membership } from "@calcom/prisma/zod";
export const schemaMembershipBodyParams = Membership.omit({});
export const schemaMembershipPublic = Membership.omit({});
export const withValidMembership = withValidation({
schema: schemaMembershipBodyParams,
type: "Zod",
mode: "body",
});
export { schemaMembership, withValidMembership };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaPayment = z.object({}).strict();
const withValidPayment = withValidation({
schema: schemaPayment,
import { _PaymentModel as Payment } from "@calcom/prisma/zod";
export const schemaPaymentBodyParams = Payment.omit({ id: true });
export const schemaPaymentPublic = Payment.omit({});
export const withValidPayment = withValidation({
schema: schemaPaymentBodyParams,
type: "Zod",
mode: "body",
});
export { schemaPayment, withValidPayment };

View File

@ -1,11 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaSchedule = z.object({}).strict();
const withValidSchedule = withValidation({
schema: schemaSchedule,
import { _ScheduleModel as Schedule } from "@calcom/prisma/zod";
export const schemaScheduleBodyParams = Schedule.omit({ id: true });
export const schemaSchedulePublic = Schedule.omit({});
export const withValidSchedule = withValidation({
schema: schemaScheduleBodyParams,
type: "Zod",
mode: "body",
});
export { schemaSchedule, withValidSchedule };

View File

@ -1,12 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaWebhook = z.object({}).strict();
import { _WebhookModel as Webhook } from "@calcom/prisma/zod";
const withValidWebhook = withValidation({
schema: schemaWebhook,
export const schemaWebhookBodyParams = Webhook.omit({ id: true });
export const schemaWebhookPublic = Webhook.omit({});
export const withValidWebhook = withValidation({
schema: schemaWebhookBodyParams,
type: "Zod",
mode: "body",
});
export { schemaWebhook, withValidWebhook };

View File

@ -1,11 +1,10 @@
// import { NextApiResponse } from "next";
import { NextRequest, NextResponse } from "next/server";
// Not much useful yet as prisma.client can't be used in the middlewares (client is not available)
// For now we just throw early if no apiKey is passed,
// but we could also check if the apiKey is valid if we had prisma here.
export default async function requireApiKeyAsQueryParams({ nextUrl }: NextRequest, res: NextResponse) {
export default async function requireApiKeyAsQueryParams({ nextUrl }: NextRequest) {
const response = NextResponse.next();
const apiKey = nextUrl.searchParams.get("apiKey");

View File

@ -2,29 +2,40 @@ import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { BaseResponse } from "@lib/types";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
message?: string;
error?: unknown;
};
/**
* @swagger
* /api/eventTypes/:id/delete:
* delete:
* description: Remove an existing eventType
* responses:
* 201:
* description: OK, eventType removed successfuly
* model: EventType
* 400:
* description: Bad request. EventType id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
export async function deleteEventType(req: NextApiRequest, res: NextApiResponse<BaseResponse>) {
const safe = await schemaQueryIdParseInt.safeParse(req.query);
if (!safe.success) throw new Error("Invalid request query", safe.error);
export async function deleteEventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const eventType = await prisma.eventType.delete({ where: { id: safe.data.id } });
// We only remove the eventType type from the database if there's an existing resource.
if (eventType)
res.status(200).json({ message: `eventType with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.eventType.delete() if the resource is not found.
else res.status(400).json({ message: `Resource with id:${safe.data.id} was not found` });
// Reject any other HTTP method than POST
} else
res.status(405).json({ message: "Only DELETE Method allowed in /availabilities/[id]/delete endpoint" });
const data = await prisma.eventType.delete({ where: { id: safe.data.id } });
if (data) res.status(200).json({ message: `EventType with id: ${safe.data.id} deleted successfully` });
else
(error: Error) =>
res.status(400).json({
message: `EventType with id: ${safe.data.id} was not able to be processed`,
error,
});
}
export default withValidQueryIdTransformParseInt(deleteEventType);
export default withMiddleware("HTTP_DELETE")(withValidQueryIdTransformParseInt(deleteEventType));

View File

@ -1,45 +1,53 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { EventType } from "@calcom/prisma/client";
import { schemaEventType, withValidEventType } from "@lib/validations/eventType";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { EventTypeResponse } from "@lib/types";
import {
schemaEventTypeBodyParams,
schemaEventTypePublic,
withValidEventType,
} from "@lib/validations/eventType";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: EventType;
message?: string;
error?: unknown;
};
/**
* @swagger
* /api/eventTypes/:id/edit:
* patch:
* description: Edits an existing eventType
* responses:
* 201:
* description: OK, eventType edited successfuly
* model: EventType
* 400:
* description: Bad request. EventType body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
export async function editEventType(req: NextApiRequest, res: NextApiResponse<EventTypeResponse>) {
const safeQuery = await schemaQueryIdParseInt.safeParse(req.query);
const safeBody = await schemaEventTypeBodyParams.safeParse(req.body);
export async function editEventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaEventType.safeParse(body);
if (!safeQuery.success || !safeBody.success) throw new Error("Invalid request");
const eventType = await prisma.eventType.update({
where: { id: safeQuery.data.id },
data: safeBody.data,
});
const data = schemaEventTypePublic.parse(eventType);
if (method === "PATCH") {
if (safeQuery.success && safeBody.success) {
await prisma.eventType
.update({
where: { id: safeQuery.data.id },
data: safeBody.data,
})
.then((event) => {
res.status(200).json({ data: event });
})
.catch((error) => {
res
.status(404)
.json({ message: `Event type with ID ${safeQuery.data.id} not found and wasn't updated`, error });
});
}
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only PATCH Method allowed for updating event-types" });
}
if (data) res.status(200).json({ data });
else
(error: Error) =>
res.status(404).json({
message: `Event type with ID ${safeQuery.data.id} not found and wasn't updated`,
error,
});
}
export default withValidQueryIdTransformParseInt(withValidEventType(editEventType));
export default withMiddleware("HTTP_PATCH")(
withValidQueryIdTransformParseInt(withValidEventType(editEventType))
);

View File

@ -1,32 +1,42 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { EventType } from "@calcom/prisma/client";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { EventTypeResponse } from "@lib/types";
import { schemaEventTypePublic } from "@lib/validations/eventType";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: EventType;
message?: string;
error?: unknown;
};
/**
* @swagger
* /api/eventTypes/:id:
* get:
* description: find eventType by ID
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: EventType was not found
*/
export async function eventTypeById(req: NextApiRequest, res: NextApiResponse<EventTypeResponse>) {
const safe = await schemaQueryIdParseInt.safeParse(req.query);
if (!safe.success) throw new Error("Invalid request query");
export async function eventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
const eventType = await prisma.eventType.findUnique({ where: { id: safe.data.id } });
const data = schemaEventTypePublic.parse(eventType);
if (method === "GET" && safe.success) {
const event = await prisma.eventType.findUnique({ where: { id: safe.data.id } });
if (event) res.status(200).json({ data: event });
if (!event) res.status(404).json({ message: "Event type not found" });
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only GET Method allowed" });
}
if (eventType) res.status(200).json({ data });
else
(error: Error) =>
res.status(404).json({
message: "EventType was not found",
error,
});
}
export default withValidQueryIdTransformParseInt(eventType);
export default withMiddleware("HTTP_GET")(withValidQueryIdTransformParseInt(eventTypeById));

View File

@ -1,21 +1,35 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { EventType } from "@calcom/prisma/client";
type ResponseData = {
data?: EventType[];
message?: string;
error?: unknown;
};
import { withMiddleware } from "@lib/helpers/withMiddleware";
import { EventTypesResponse } from "@lib/types";
import { schemaEventTypePublic } from "@lib/validations/eventType";
export default async function eventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { method } = req;
if (method === "GET") {
const data = await prisma.eventType.findMany();
res.status(200).json({ data });
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only GET Method allowed" });
}
/**
* @swagger
* /api/eventTypes:
* get:
* description: Returns all eventTypes
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No eventTypes were found
*/
async function allEventTypes(_: NextApiRequest, res: NextApiResponse<EventTypesResponse>) {
const eventTypes = await prisma.eventType.findMany();
const data = eventTypes.map((eventType) => schemaEventTypePublic.parse(eventType));
if (data) res.status(200).json({ data });
else
(error: Error) =>
res.status(404).json({
message: "No EventTypes were found",
error,
});
}
export default withMiddleware("HTTP_GET")(allEventTypes);

View File

@ -1,30 +1,43 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { EventType } from "@calcom/prisma/client";
import { schemaEventType, withValidEventType } from "@lib/validations/eventType";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { EventTypeResponse } from "@lib/types";
import {
schemaEventTypeBodyParams,
schemaEventTypePublic,
withValidEventType,
} from "@lib/validations/eventType";
type ResponseData = {
data?: EventType;
message?: string;
error?: string;
};
/**
* @swagger
* /api/eventTypes/new:
* post:
* description: Creates a new eventType
* responses:
* 201:
* description: OK, eventType created
* model: EventType
* 400:
* description: Bad request. EventType body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
async function createEventType(req: NextApiRequest, res: NextApiResponse<EventTypeResponse>) {
const safe = schemaEventTypeBodyParams.safeParse(req.body);
if (!safe.success) throw new Error("Invalid request body", safe.error);
async function createEventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { body, method } = req;
if (method === "POST") {
const safe = schemaEventType.safeParse(body);
if (safe.success && safe.data) {
await prisma.eventType
.create({ data: safe.data })
.then((event) => res.status(201).json({ data: event }))
.catch((error) => res.status(400).json({ message: "Could not create event type", error: error }));
}
} else {
// Reject any other HTTP method than POST
res.status(405).json({ error: "Only POST Method allowed" });
}
const eventType = await prisma.eventType.create({ data: safe.data });
const data = schemaEventTypePublic.parse(eventType);
if (data) res.status(201).json({ data, message: "EventType created successfully" });
else
(error: Error) =>
res.status(400).json({
message: "Could not create new eventType",
error,
});
}
export default withValidEventType(createEventType);
export default withMiddleware("HTTP_POST")(withValidEventType(createEventType));