Merge pull request #8 from calcom/feat/validate-add-availabilites-and-attendees

feat: adds availabilities and attendees endpoints
This commit is contained in:
Agusti Fernandez
2022-03-28 16:21:57 +02:00
committed by GitHub
59 changed files with 913 additions and 287 deletions
+1
View File
@@ -45,6 +45,7 @@ yarn-error.log*
.idea
### VisualStudioCode template
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
+1 -5
View File
@@ -1,8 +1,4 @@
export const stringifyISODate = (date: Date|undefined): string => {
return `${date?.toISOString()}`
}
export const autoStringifyDateValues = ([key, value]: [string, unknown]): [string, unknown] => {
console.log(key,value)
return [key, typeof value === "object" && value instanceof Date ? stringifyISODate(value) : value]
}
// TODO: create a function that takes an object and returns a stringified version of dates of it.
+20
View File
@@ -0,0 +1,20 @@
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,
type: "Zod",
mode: "body",
});
export { schemaAttendee, withValidAttendee };
+23
View File
@@ -0,0 +1,23 @@
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(),
days: z.array(z.number()),
date: z.date().or(z.string()),
startTime: z.string(),
endTime: z.string(),
})
.strict();
const withValidAvailability = withValidation({
schema: schemaAvailability,
type: "Zod",
mode: "body",
});
export { schemaAvailability, withValidAvailability };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaBookingReference = z
.object({})
.strict();
const withValidBookingReference = withValidation({
schema: schemaBookingReference,
type: "Zod",
mode: "body",
});
export { schemaBookingReference, withValidBookingReference };
+2 -42
View File
@@ -10,52 +10,12 @@ const schemaBooking = z
endTime: z.date(),
location: z.string().min(3).optional(),
createdAt: z.date().or(z.string()),
updatedAt: z.date(),
updatedAt: z.date().or(z.string()),
confirmed: z.boolean().default(true),
rejected: z.boolean().default(false),
paid: z.boolean().default(false),
// bufferTime: z.number().default(0),
// // attendees: z.array((schemaSchedule)).optional(),
// startTime: z.string().min(3),
// endTime: z.string().min(3),
// email: z.string().email(), // max is a full day.
// emailVerified: z.date().optional(),
// password: z.string().optional(),
// bio: z.string().min(3).optional(),
// avatar: z.string().optional(),
// timeZone: z.string().default("Europe/London"),
// weekStart: z.string().default("Sunday"),
// bufferTime: z.number().default(0),
// theme: z.string().optional(),
// trialEndsAt: z.date().optional(),
// eventTypes: z.array((schemaEventType)).optional(),
// // credentials: z.array((schemaCredentials)).optional(),
// // teams: z.array((schemaMembership)).optional(),
// // bookings: z.array((schemaBooking)).optional(),
// // schedules: z.array((schemaSchedule)).optional(),
// defaultScheduleId: z.number().optional(),
// // selectedCalendars: z.array((schemaSelectedCalendar)).optional(),
// completedOnboarding: z.boolean().default(false),
// locale: z.string().optional(),
// timeFormat: z.number().optional().default(12),
// twoFactorEnabled: z.boolean().default(false),
// twoFactorSecret: z.string().optional(),
// identityProvider: z.enum(["CAL", "SAML", "GOOGLE"]).optional().default("CAL"),
// identityProviderId: z.string().optional(),
// // availavility: z.array((schemaAvailavility)).optional(),
// invitedTo: z.number().optional(),
// plan: z.enum(['FREE', 'TRIAL', 'PRO']).default("TRIAL"),
// // webhooks: z.array((schemaWebhook)).optional(),
// brandColor: z.string().default("#292929"),
// darkBrandColor: z.string().default("#fafafa"),
// // destinationCalendar: z.instanceof(schemaEventType).optional(), // FIXME: instanceof doesnt work here
// away: z.boolean().default(false),
// metadata: z.object({}).optional(),
// verified: z.boolean().default(false),
})
.strict(); // Adding strict so that we can disallow passing in extra fields
.strict();
const withValidBooking = withValidation({
schema: schemaBooking,
type: "Zod",
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaCredential = z
.object({})
.strict();
const withValidCredential = withValidation({
schema: schemaCredential,
type: "Zod",
mode: "body",
});
export { schemaCredential, withValidCredential };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaDailyEventReference = z
.object({})
.strict();
const withValidDailyEventReference = withValidation({
schema: schemaDailyEventReference,
type: "Zod",
mode: "body",
});
export { schemaDailyEventReference, withValidDailyEventReference };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaDestinationCalendar = z
.object({})
.strict();
const withValidDestinationCalendar = withValidation({
schema: schemaDestinationCalendar,
type: "Zod",
mode: "body",
});
export { schemaDestinationCalendar, withValidDestinationCalendar };
+1 -1
View File
@@ -8,7 +8,7 @@ const schemaEventType = z
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
.strict();
const withValidEventType = withValidation({
schema: schemaEventType,
type: "Zod",
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaMembership = z
.object({})
.strict();
const withValidMembership = withValidation({
schema: schemaMembership,
type: "Zod",
mode: "body",
});
export { schemaMembership, withValidMembership };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaPayment = z
.object({})
.strict();
const withValidPayment = withValidation({
schema: schemaPayment,
type: "Zod",
mode: "body",
});
export { schemaPayment, withValidPayment };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaSchedule = z
.object({})
.strict();
const withValidSchedule = withValidation({
schema: schemaSchedule,
type: "Zod",
mode: "body",
});
export { schemaSchedule, withValidSchedule };
+13
View File
@@ -0,0 +1,13 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaSelectedCalendar = z
.object({})
.strict();
const withValidSelectedCalendar = withValidation({
schema: schemaSelectedCalendar,
type: "Zod",
mode: "body",
});
export { schemaSelectedCalendar, withValidSelectedCalendar };
@@ -3,7 +3,7 @@ import { z } from "zod";
// Extracted out as utility function so can be reused
// at different endpoints that require this validation.
const schemaQueryId = z
const schemaQueryIdParseInt = z
.object({
// since nextjs parses query params as strings,
// we need to cast them to numbers using z.transform() and parseInt()
@@ -15,9 +15,9 @@ const schemaQueryId = z
.strict();
const withValidQueryIdTransformParseInt = withValidation({
schema: schemaQueryId,
schema: schemaQueryIdParseInt,
type: "Zod",
mode: "query",
});
export { schemaQueryId, withValidQueryIdTransformParseInt };
export { schemaQueryIdParseInt, withValidQueryIdTransformParseInt };
+1 -1
View File
@@ -9,7 +9,7 @@ const schemaTeam = z
bio: z.string().min(3).optional(),
logo: z.string().optional(),
})
.strict(); // Adding strict so that we can disallow passing in extra fields
.strict();
const withValidTeam = withValidation({
schema: schemaTeam,
type: "Zod",
+19 -18
View File
@@ -1,15 +1,16 @@
import { withValidation } from "next-validations";
import { schemaEventType } from "./eventType";
// import { schemaCredential } from "./credential";
// import { schemaMembership } from "./membership";
// import { schemaBooking } from "./booking";
// import { schemaSchedule } from "./schedule";
// import { schemaSelectedCalendar } from "./selectedCalendar";
// import { schemaAvailability } from "./availability";
// import { schemaWebhook } from "./webhook";
import { z } from "zod";
import { schemaEventType } from "./eventType";
import { schemaApiKey } from "./apiKey";
import { schemaDestinationCalendar } from "./destination-calendar";
import { schemaWebhook } from "./webhook";
import { schemaAvailability } from "./availability";
import { schemaSelectedCalendar } from "./selected-calendar";
import { schemaBooking } from "./booking";
import { schemaMembership } from "./membership";
import { schemaSchedule } from "./schedule";
import { schemaCredential } from "./credential";
const schemaUser = z
.object({
@@ -27,12 +28,12 @@ const schemaUser = z
theme: z.string().optional(),
trialEndsAt: z.date().optional(),
eventTypes: z.array((schemaEventType)).optional(),
// credentials: z.array((schemaCredentials)).optional(),
// teams: z.array((schemaMembership)).optional(),
// bookings: z.array((schemaBooking)).optional(),
// schedules: z.array((schemaSchedule)).optional(),
credentials: z.array((schemaCredential)).optional(),
teams: z.array((schemaMembership)).optional(),
bookings: z.array((schemaBooking)).optional(),
schedules: z.array((schemaSchedule)).optional(),
defaultScheduleId: z.number().optional(),
// selectedCalendars: z.array((schemaSelectedCalendar)).optional(),
selectedCalendars: z.array((schemaSelectedCalendar)).optional(),
completedOnboarding: z.boolean().default(false),
locale: z.string().optional(),
timeFormat: z.number().optional().default(12),
@@ -40,19 +41,19 @@ const schemaUser = z
twoFactorSecret: z.string().optional(),
identityProvider: z.enum(["CAL", "SAML", "GOOGLE"]).optional().default("CAL"),
identityProviderId: z.string().optional(),
// availavility: z.array((schemaAvailavility)).optional(),
availability: z.array((schemaAvailability)).optional(),
invitedTo: z.number().optional(),
plan: z.enum(['FREE', 'TRIAL', 'PRO']).default("TRIAL"),
// webhooks: z.array((schemaWebhook)).optional(),
webhooks: z.array((schemaWebhook)).optional(),
brandColor: z.string().default("#292929"),
darkBrandColor: z.string().default("#fafafa"),
// destinationCalendar: z.instanceof(schemaEventType).optional(), // FIXME: instanceof doesnt work here
destinationCalendar: z.array(schemaDestinationCalendar).optional(), // FIXME: instanceof doesnt work here
away: z.boolean().default(false),
metadata: z.object({}).optional(),
verified: z.boolean().default(false),
apiKeys: z.array((schemaApiKey)).optional(),
})
.strict(); // Adding strict so that we can disallow passing in extra fields
.strict();
const withValidUser = withValidation({
schema: schemaUser,
type: "Zod",
+14
View File
@@ -0,0 +1,14 @@
import { withValidation } from "next-validations";
import { z } from "zod";
const schemaWebhook = z
.object({})
.strict();
const withValidWebhook = withValidation({
schema: schemaWebhook,
type: "Zod",
mode: "body",
});
export { schemaWebhook, withValidWebhook };
+16
View File
@@ -0,0 +1,16 @@
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 async function middleware({ nextUrl }: NextRequest) {
const response = NextResponse.next();
const apiKey = nextUrl.searchParams.get("apiKey");
if (apiKey) return response;
// if no apiKey is passed, we throw early
else
throw new Error(
"You need to pass an apiKey as query param: https://api.cal.com/resource?apiKey=<your-api-key>"
);
}
+12 -17
View File
@@ -1,8 +1,10 @@
import prisma from "@calcom/prisma";
import { NextApiRequest, NextApiResponse } from "next";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryIdAsString, withValidQueryIdString } from "@lib/validations/shared/queryIdString";
type ResponseData = {
message?: string;
error?: unknown;
@@ -11,22 +13,15 @@ type ResponseData = {
export async function apiKey(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdAsString.safeParse(query);
if (method === "DELETE" && safe.success) {
// DELETE WILL DELETE THE EVENT TYPE
await prisma.apiKey
.delete({ where: { id: safe.data.id } })
.then(() => {
// We only remove the api key from the database if there's an existing resource.
res.status(204).json({ message: `api-key with id: ${safe.data.id} deleted successfully` });
})
.catch((error) => {
// This catches the error thrown by prisma.apiKey.delete() if the resource is not found.
res.status(404).json({ message: `Resource with id:${safe.data.id} was not found`, error: error });
});
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only DELETE Method allowed" });
}
if (method === "DELETE" && safe.success && safe.data) {
const apiKey = await prisma.apiKey
.delete({ where: { id: safe.data.id } })
// We only remove the apiKey type from the database if there's an existing resource.
if (apiKey) res.status(200).json({ message: `apiKey with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.apiKey.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" });
}
export default withValidQueryIdString(apiKey);
+2 -4
View File
@@ -26,10 +26,8 @@ export async function editApiKey(req: NextApiRequest, res: NextApiResponse<Respo
}).catch(error => {
res.status(404).json({ message: `apiKey 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 API keys" });
}
// Reject any other HTTP method than POST
} else res.status(405).json({ message: "Only PATCH Method allowed for updating API keys" });
}
export default withValidQueryIdString(withValidApiKey(editApiKey));
+2 -4
View File
@@ -18,10 +18,8 @@ export async function apiKey(req: NextApiRequest, res: NextApiResponse<ResponseD
const apiKey = await prisma.apiKey.findUnique({ where: { id: safe.data.id } });
if (!apiKey) res.status(404).json({ message: "API key was not found" });
else res.status(200).json({ data: apiKey });
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only GET Method allowed" });
}
// Reject any other HTTP method than POST
} else res.status(405).json({ message: "Only GET Method allowed" });
}
+3 -13
View File
@@ -12,17 +12,7 @@ type ResponseData = {
export default async function apiKeys(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { method } = req;
if (method === "GET") {
// try {
const apiKeys = await prisma.apiKey.findMany({});
res.status(200).json({ data: { ...apiKeys } });
// Without any params this never fails. not sure how to force test unavailable prisma query
// } catch (error) {
// // FIXME: Add zod for validation/error handling
// res.status(400).json({ error: error });
// }
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only GET Method allowed" });
}
const data = await prisma.apiKey.findMany({});
res.status(200).json({ data });
} else res.status(405).json({ message: "Only GET Method allowed" });
}
+7 -15
View File
@@ -15,21 +15,13 @@ async function createApiKey(req: NextApiRequest, res: NextApiResponse<ResponseDa
const { body, method } = req;
const safe = schemaApiKey.safeParse(body);
if (method === "POST" && safe.success) {
const apiKey = await prisma.apiKey
.create({
data: {
...safe.data, user: { connect: { id: 1 } }
}
})
if (apiKey) {
res.status(201).json({ data: apiKey });
} else {
res.status(404).json({message: "API Key not created"});
}
} else {
// Reject any other HTTP method than POST
res.status(405).json({ error: "Only POST Method allowed" });
}
const apiKey = await prisma.apiKey
.create({ data: { ...safe.data, user: { connect: { id: 1 } } } })
if (apiKey) res.status(201).json({ data: apiKey });
else res.status(404).json({ message: "API Key not created" });
} else res.status(405).json({ error: "Only POST Method allowed" });
}
export default withValidApiKey(createApiKey);
+27
View File
@@ -0,0 +1,27 @@
import prisma from "@calcom/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
message?: string;
error?: unknown;
};
export async function attendee(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const attendee = await prisma.attendee
.delete({ where: { id: safe.data.id } })
// We only remove the attendee type from the database if there's an existing resource.
if (attendee) res.status(200).json({ message: `attendee with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.attendee.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" });
}
export default withValidQueryIdTransformParseInt(attendee);
+33
View File
@@ -0,0 +1,33 @@
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaAttendee, withValidAttendee } from "@lib/validations/attendee";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Attendee;
message?: string;
error?: unknown;
};
export async function editAttendee(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaAttendee.safeParse(body);
if (method === "PATCH" && safeQuery.success && safeBody.success) {
await prisma.attendee.update({
where: { id: safeQuery.data.id },
data: safeBody.data,
}).then(attendee => {
res.status(200).json({ data: attendee });
}).catch(error => {
res.status(404).json({ message: `Event type with ID ${safeQuery.data.id} not found and wasn't updated`, error })
});
// Reject any other HTTP method than POST
} else res.status(405).json({ message: "Only PATCH Method allowed for updating attendees" });
}
export default withValidQueryIdTransformParseInt(withValidAttendee(editAttendee));
+27
View File
@@ -0,0 +1,27 @@
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Attendee;
message?: string;
error?: unknown;
};
export async function attendee(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "GET" && safe.success) {
const attendee = await prisma.attendee.findUnique({ where: { id: safe.data.id } });
if (attendee) res.status(200).json({ data: attendee });
if (!attendee) res.status(404).json({ message: "Event type not found" });
// Reject any other HTTP method than POST
} else res.status(405).json({ message: "Only GET Method allowed" });
}
export default withValidQueryIdTransformParseInt(attendee);
+19
View File
@@ -0,0 +1,19 @@
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
type ResponseData = {
data?: Attendee[];
error?: unknown;
};
export default async function attendee(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
try {
const data = await prisma.attendee.findMany();
res.status(200).json({ data });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
}
}
+27
View File
@@ -0,0 +1,27 @@
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaAttendee, withValidAttendee } from "@lib/validations/attendee";
type ResponseData = {
data?: Attendee;
message?: string;
error?: string;
};
async function createAttendee(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { body, method } = req;
const safe = schemaAttendee.safeParse(body);
if (method === "POST" && safe.success) {
await prisma.attendee
.create({ data: safe.data })
.then((attendee) => res.status(201).json({ data: attendee }))
.catch((error) => res.status(400).json({ message: "Could not create attendee type", error: error }));
// Reject any other HTTP method than POST
} else res.status(405).json({ error: "Only POST Method allowed" });
}
export default withValidAttendee(createAttendee);
+26
View File
@@ -0,0 +1,26 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
message?: string;
error?: unknown;
};
export async function availability(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const availability = await prisma.availability
.delete({ where: { id: safe.data.id } })
// We only remove the availability type from the database if there's an existing resource.
if (availability) res.status(200).json({ message: `availability with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.availability.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" });
}
export default withValidQueryIdTransformParseInt(availability);
+33
View File
@@ -0,0 +1,33 @@
import prisma from "@calcom/prisma";
import { Availability } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaAvailability, withValidAvailability,} from "@lib/validations/availability";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Availability;
message?: string;
error?: unknown;
};
export async function editAvailability(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaAvailability.safeParse(body);
if (method === "PATCH" && safeQuery.success && safeBody.success) {
await prisma.availability.update({
where: { id: safeQuery.data.id },
data: safeBody.data,
}).then(availability => {
res.status(200).json({ data: availability });
}).catch(error => {
res.status(404).json({ message: `Event type with ID ${safeQuery.data.id} not found and wasn't updated`, error })
});
// Reject any other HTTP method than PATCH
} else res.status(405).json({ message: "Only PATCH Method allowed for updating availabilities" });
}
export default withValidQueryIdTransformParseInt(withValidAvailability(editAvailability));
+28
View File
@@ -0,0 +1,28 @@
import prisma from "@calcom/prisma";
import { Availability } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Availability;
message?: string;
error?: unknown;
};
export async function availability(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "GET" && safe.success) {
const availability = await prisma.availability.findUnique({ where: { id: safe.data.id } });
if (availability) res.status(200).json({ data: availability });
if (!availability) res.status(404).json({ message: "Event type not found" });
// Reject any other HTTP method than POST
} else res.status(405).json({ message: "Only GET Method allowed" });
}
export default withValidQueryIdTransformParseInt(availability);
+19
View File
@@ -0,0 +1,19 @@
import prisma from "@calcom/prisma";
import { Availability } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
type ResponseData = {
data?: Availability[];
error?: unknown;
};
export default async function availability(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
try {
const data = await prisma.availability.findMany();
res.status(200).json({ data });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
}
}
+30
View File
@@ -0,0 +1,30 @@
import prisma from "@calcom/prisma";
import { Availability } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaAvailability, withValidAvailability } from "@lib/validations/availability";
type ResponseData = {
data?: Availability;
message?: string;
error?: string;
};
async function createAvailability(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { body, method } = req;
if (method === "POST") {
const safe = schemaAvailability.safeParse(body);
if (safe.success && safe.data) {
await prisma.availability
.create({ data: safe.data })
.then((availability) => res.status(201).json({ data: availability }))
.catch((error) => res.status(400).json({ message: "Could not create availability type", error: error }));
}
} else {
// Reject any other HTTP method than POST
res.status(405).json({ error: "Only POST Method allowed" });
}
}
export default withValidAvailability(createAvailability);
+13 -22
View File
@@ -2,7 +2,7 @@ import prisma from "@calcom/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
@@ -10,27 +10,18 @@ type ResponseData = {
error?: unknown;
};
export async function booking(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
export async function deleteBooking(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "DELETE") {
// DELETE WILL DELETE THE EVENT TYPE
prisma.booking
.delete({ where: { id: safe.data.id } })
.then(() => {
// We only remove the booking type from the database if there's an existing resource.
res.status(200).json({ message: `booking-type with id: ${safe.data.id} deleted successfully` });
})
.catch((error) => {
// This catches the error thrown by prisma.booking.delete() if the resource is not found.
res.status(400).json({ message: `Resource with id:${safe.data.id} was not found`, error: error });
});
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only DELETE Method allowed in /booking-types/[id]/delete endpoint" });
}
}
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const booking = await prisma.booking
.delete({ where: { id: safe.data.id } })
// We only remove the booking type from the database if there's an existing resource.
if (booking) res.status(200).json({ message: `booking with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.booking.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" });
}
export default withValidQueryIdTransformParseInt(booking);
export default withValidQueryIdTransformParseInt(deleteBooking);
+2 -2
View File
@@ -4,7 +4,7 @@ import { Booking } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaBooking, withValidBooking } from "@lib/validations/booking";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Booking;
@@ -14,7 +14,7 @@ type ResponseData = {
export async function editBooking(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryId.safeParse(query);
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaBooking.safeParse(body);
if (method === "PATCH") {
+10 -11
View File
@@ -3,7 +3,7 @@ import prisma from "@calcom/prisma";
import { Booking } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Booking;
@@ -13,17 +13,16 @@ type ResponseData = {
export async function booking(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "GET") {
const booking = await prisma.booking.findUnique({ where: { id: safe.data.id } });
const safe = await schemaQueryIdParseInt.safeParse(query);
if (booking) res.status(200).json({ data: booking });
if (!booking) 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 (method === "GET" && safe.success) {
const booking = await prisma.booking.findUnique({ where: { id: safe.data.id } });
if (booking) res.status(200).json({ data: booking });
if (!booking) 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" });
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ type ResponseData = {
export default async function booking(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
try {
const bookings = await prisma.booking.findMany();
res.status(200).json({ data: { ...bookings } });
const data = await prisma.booking.findMany();
res.status(200).json({ data });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
+13 -22
View File
@@ -2,7 +2,7 @@ import prisma from "@calcom/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
@@ -10,27 +10,18 @@ type ResponseData = {
error?: unknown;
};
export async function eventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
export async function deleteEventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "DELETE") {
// DELETE WILL DELETE THE EVENT TYPE
prisma.eventType
.delete({ where: { id: safe.data.id } })
.then(() => {
// We only remove the event type from the database if there's an existing resource.
res.status(200).json({ message: `event-type with id: ${safe.data.id} deleted successfully` });
})
.catch((error) => {
// This catches the error thrown by prisma.eventType.delete() if the resource is not found.
res.status(400).json({ message: `Resource with id:${safe.data.id} was not found`, error: error });
});
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only DELETE Method allowed in /event-types/[id]/delete endpoint" });
}
}
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" });
}
export default withValidQueryIdTransformParseInt(eventType);
export default withValidQueryIdTransformParseInt(deleteEventType);
+2 -2
View File
@@ -4,7 +4,7 @@ import { EventType } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaEventType, withValidEventType } from "@lib/validations/eventType";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: EventType;
@@ -14,7 +14,7 @@ type ResponseData = {
export async function editEventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryId.safeParse(query);
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaEventType.safeParse(body);
if (method === "PATCH") {
+10 -11
View File
@@ -3,7 +3,7 @@ import prisma from "@calcom/prisma";
import { EventType } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: EventType;
@@ -13,17 +13,16 @@ type ResponseData = {
export async function eventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "GET") {
const event = await prisma.eventType.findUnique({ where: { id: safe.data.id } });
const safe = await schemaQueryIdParseInt.safeParse(query);
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 (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" });
}
}
+2 -2
View File
@@ -12,8 +12,8 @@ type ResponseData = {
export default async function eventType(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { method } = req;
if (method === "GET") {
const eventTypes = await prisma.eventType.findMany();
res.status(200).json({ data: { ...eventTypes } });
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" });
+13 -22
View File
@@ -2,7 +2,7 @@ import prisma from "@calcom/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
@@ -10,27 +10,18 @@ type ResponseData = {
error?: unknown;
};
export async function team(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
export async function deleteTeam(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "DELETE") {
// DELETE WILL DELETE THE EVENT TYPE
prisma.team
.delete({ where: { id: safe.data.id } })
.then(() => {
// We only remove the team type from the database if there's an existing resource.
res.status(200).json({ message: `team-type with id: ${safe.data.id} deleted successfully` });
})
.catch((error) => {
// This catches the error thrown by prisma.team.delete() if the resource is not found.
res.status(400).json({ message: `Resource with id:${safe.data.id} was not found`, error: error });
});
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only DELETE Method allowed in /team-types/[id]/delete endpoint" });
}
}
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const team = await prisma.team
.delete({ where: { id: safe.data.id } })
// We only remove the team type from the database if there's an existing resource.
if (team) res.status(200).json({ message: `team with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.team.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" });
}
export default withValidQueryIdTransformParseInt(team);
export default withValidQueryIdTransformParseInt(deleteTeam);
+2 -2
View File
@@ -4,7 +4,7 @@ import { Team } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaTeam, withValidTeam } from "@lib/validations/team";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Team;
@@ -14,7 +14,7 @@ type ResponseData = {
export async function editTeam(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryId.safeParse(query);
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaTeam.safeParse(body);
if (method === "PATCH") {
+10 -11
View File
@@ -3,7 +3,7 @@ import prisma from "@calcom/prisma";
import { Team } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: Team;
@@ -13,17 +13,16 @@ type ResponseData = {
export async function team(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "GET") {
const team = await prisma.team.findUnique({ where: { id: safe.data.id } });
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "GET" && safe.success) {
const team = await prisma.team.findUnique({ where: { id: safe.data.id } });
if (team) res.status(200).json({ data: team });
if (!team) 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 (team) res.status(200).json({ data: team });
if (!team) 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" });
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ type ResponseData = {
export default async function team(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
try {
const teams = await prisma.team.findMany();
res.status(200).json({ data: { ...teams } });
const data = await prisma.team.findMany();
res.status(200).json({ data });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
+13 -22
View File
@@ -2,7 +2,7 @@ import prisma from "@calcom/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
@@ -10,27 +10,18 @@ type ResponseData = {
error?: unknown;
};
export async function user(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
export async function deleteUser(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "DELETE") {
// DELETE WILL DELETE THE EVENT TYPE
prisma.user
.delete({ where: { id: safe.data.id } })
.then(() => {
// We only remove the user type from the database if there's an existing resource.
res.status(200).json({ message: `user-type with id: ${safe.data.id} deleted successfully` });
})
.catch((error) => {
// This catches the error thrown by prisma.user.delete() if the resource is not found.
res.status(400).json({ message: `Resource with id:${safe.data.id} was not found`, error: error });
});
} else {
// Reject any other HTTP method than POST
res.status(405).json({ message: "Only DELETE Method allowed in /user-types/[id]/delete endpoint" });
}
}
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "DELETE" && safe.success && safe.data) {
const user = await prisma.user
.delete({ where: { id: safe.data.id } })
// We only remove the user type from the database if there's an existing resource.
if (user) res.status(200).json({ message: `user with id: ${safe.data.id} deleted successfully` });
// This catches the error thrown by prisma.user.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" });
}
export default withValidQueryIdTransformParseInt(user);
export default withValidQueryIdTransformParseInt(deleteUser);
+2 -2
View File
@@ -4,7 +4,7 @@ import { User } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaUser, withValidUser } from "@lib/validations/user";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: User;
@@ -14,7 +14,7 @@ type ResponseData = {
export async function editUser(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, body, method } = req;
const safeQuery = await schemaQueryId.safeParse(query);
const safeQuery = await schemaQueryIdParseInt.safeParse(query);
const safeBody = await schemaUser.safeParse(body);
if (method === "PATCH") {
+9 -11
View File
@@ -3,7 +3,7 @@ import prisma from "@calcom/prisma";
import { User } from "@calcom/prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { schemaQueryId, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
import { schemaQueryIdParseInt, withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
type ResponseData = {
data?: User;
@@ -13,17 +13,15 @@ type ResponseData = {
export async function user(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { query, method } = req;
const safe = await schemaQueryId.safeParse(query);
if (safe.success) {
if (method === "GET") {
const user = await prisma.user.findUnique({ where: { id: safe.data.id } });
const safe = await schemaQueryIdParseInt.safeParse(query);
if (method === "GET" && safe.success) {
const user = await prisma.user.findUnique({ where: { id: safe.data.id } });
if (user) res.status(200).json({ data: user });
if (!user) 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 (user) res.status(200).json({ data: user });
if (!user) 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" });
}
}
+23 -7
View File
@@ -7,13 +7,29 @@ type ResponseData = {
data?: User[];
error?: unknown;
};
const dateInPast = function (firstDate: Date, secondDate: Date) {
if (firstDate.setHours(0, 0, 0, 0) <= secondDate.setHours(0, 0, 0, 0)) {
return true;
}
return false;
};
const today = new Date();
export default async function user(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
try {
const users = await prisma.user.findMany();
res.status(200).json({ data: { ...users } });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
}
const apiKey = req.query.apiKey as string;
const apiInDb = await prisma.apiKey.findUnique({ where: { id: apiKey } });
if (!apiInDb) throw new Error('API key not found');
const { expiresAt } = apiInDb;
// if (!apiInDb) res.status(400).json({ error: 'Your api key is not valid' });
if (expiresAt && dateInPast(expiresAt, today)) {
try {
const data = await prisma.user.findMany();
res.status(200).json({ data });
} catch (error) {
// FIXME: Add zod for validation/error handling
res.status(400).json({ error: error });
}
} else res.status(400).json({ error: 'Your api key is not valid' });
}
+4 -8
View File
@@ -13,18 +13,14 @@ type ResponseData = {
async function createUser(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
const { body, method } = req;
if (method === "POST") {
const safe = schemaUser.safeParse(body);
if (safe.success && safe.data) {
const safe = schemaUser.safeParse(body);
if (method === "POST" && safe.success) {
await prisma.user
.create({ data: safe.data })
.then((user) => res.status(201).json({ data: user }))
.catch((error) => res.status(400).json({ message: "Could not create user type", error: error }));
}
} else {
// Reject any other HTTP method than POST
res.status(405).json({ error: "Only POST Method allowed" });
}
// Reject any other HTTP method than POST
} else res.status(405).json({ error: "Only POST Method allowed" });
}
export default withValidUser(createUser);
@@ -14,8 +14,6 @@ describe("DELETE /api/api-keys/[id]/delete with valid id as string returns an ap
});
// const apiKey = await prisma.apiKey.findUnique({ where: { id: req.query.id} });
await handleDeleteApiKey(req, res);
// console.log(res)
expect(res._getStatusCode()).toBe(204);
expect(JSON.parse(res._getData())).toEqual({message: `api-key with id: ${apiKey?.id} deleted successfully`});
});
@@ -0,0 +1,92 @@
import handleBookingEdit from "@api/bookings/[id]/edit";
import { createMocks } from "node-mocks-http";
import prisma from "@calcom/prisma";
describe("PATCH /api/bookings/[id]/edit with valid id and body updates an booking", () => {
it("returns a message with the specified bookings", async () => {
const { req, res } = createMocks({
method: "PATCH",
query: {
id: "2",
},
body: {
title: "Updated title",
slug: "updated-slug",
length: 1,
},
});
const booking = await prisma.booking.findUnique({ where: { id: parseInt(req.query.id) } });
await handleBookingEdit(req, res);
expect(res._getStatusCode()).toBe(200);
if (booking) booking.title = "Updated title";
expect(JSON.parse(res._getData())).toStrictEqual({ data: booking });
});
});
describe("PATCH /api/bookings/[id]/edit with invalid id returns 404", () => {
it("returns a message with the specified bookings", async () => {
const { req, res } = createMocks({
method: "PATCH",
query: {
id: "0",
},
body: {
title: "Updated title",
slug: "updated-slug",
length: 1,
},
});
const booking = await prisma.booking.findUnique({ where: { id: parseInt(req.query.id) } });
await handleBookingEdit(req, res);
expect(res._getStatusCode()).toBe(404);
if (booking) booking.title = "Updated title";
expect(JSON.parse(res._getData())).toStrictEqual({ "error": {
"clientVersion": "3.10.0",
"code": "P2025",
"meta": {
"cause": "Record to update not found.",
},
},
"message": "Event type with ID 0 not found and wasn't updated", });
});
});
describe("PATCH /api/bookings/[id]/edit with valid id and no body returns 400 error and zod validation errors", () => {
it("returns a message with the specified bookings", async () => {
const { req, res } = createMocks({
method: "PATCH",
query: {
id: "2",
},
});
await handleBookingEdit(req, res);
expect(res._getStatusCode()).toBe(400);
expect(JSON.parse(res._getData())).toStrictEqual([{"code": "invalid_type", "expected": "string", "message": "Required", "path": ["title"], "received": "undefined"}, {"code": "invalid_type", "expected": "string", "message": "Required", "path": ["slug"], "received": "undefined"}, {"code": "invalid_type", "expected": "number", "message": "Required", "path": ["length"], "received": "undefined"}]);
});
});
describe("POST /api/bookings/[id]/edit fails, only PATCH allowed", () => {
it("returns a message with the specified bookings", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
query: {
id: "1",
},
body: {
title: "Updated title",
slug: "updated-slug",
length: 1,
},
});
await handleBookingEdit(req, res);
expect(res._getStatusCode()).toBe(405);
expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only PATCH Method allowed for updating bookings" });
});
});
@@ -0,0 +1,85 @@
import handleBooking from "@api/bookings/[id]";
import { createMocks } from "node-mocks-http";
import prisma from "@calcom/prisma";
import { stringifyISODate } from "@lib/utils/stringifyISODate";
describe("GET /api/bookings/[id] with valid id as string returns an booking", () => {
it("returns a message with the specified events", async () => {
const { req, res } = createMocks({
method: "GET",
query: {
id: "1",
},
});
const booking = await prisma.booking.findUnique({ where: { id: 1 } });
await handleBooking(req, res);
expect(res._getStatusCode()).toBe(200);
expect(JSON.parse(res._getData())).toEqual({
data: {
...booking,
createdAt: stringifyISODate(booking?.createdAt),
startTime: stringifyISODate(booking?.startTime),
endTime: stringifyISODate(booking?.endTime)
}
});
});
});
// This can never happen under our normal nextjs setup where query is always a string | string[].
// But seemed a good example for testing an error validation
describe("GET /api/bookings/[id] errors if query id is number, requires a string", () => {
it("returns a message with the specified events", async () => {
const { req, res } = createMocks({
method: "GET",
query: {
id: 1, // passing query as a number, which should fail as nextjs will try to parse it as a string
},
});
await handleBooking(req, res);
expect(res._getStatusCode()).toBe(400);
expect(JSON.parse(res._getData())).toStrictEqual([
{
code: "invalid_type",
expected: "string",
received: "number",
path: ["id"],
message: "Expected string, received number",
},
]);
});
});
describe("GET /api/bookings/[id] an id not present in db like 0, throws 404 not found", () => {
it("returns a message with the specified events", async () => {
const { req, res } = createMocks({
method: "GET",
query: {
id: "0", // There's no booking type with id 0
},
});
await handleBooking(req, res);
expect(res._getStatusCode()).toBe(404);
expect(JSON.parse(res._getData())).toStrictEqual({ message: "Event type not found" });
});
});
describe("POST /api/bookings/[id] fails, only GET allowed", () => {
it("returns a message with the specified events", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
query: {
id: "1",
},
});
await handleBooking(req, res);
expect(res._getStatusCode()).toBe(405);
expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only GET Method allowed" });
});
});
+31
View File
@@ -0,0 +1,31 @@
import handleApiKeys from "@api/api-keys";
import { createMocks } from "node-mocks-http";
import prisma from "@calcom/prisma";
import {stringifyISODate} from "@lib/utils/stringifyISODate";
describe("GET /api/api-keys without any params", () => {
it("returns a message with the specified apiKeys", async () => {
const { req, res } = createMocks({
method: "GET",
query: {},
});
let apiKeys = await prisma.apiKey.findMany();
await handleApiKeys(req, res);
expect(res._getStatusCode()).toBe(200);
apiKeys = apiKeys.map(apiKey => (apiKey = {...apiKey, createdAt: stringifyISODate(apiKey?.createdAt), expiresAt: stringifyISODate(apiKey?.expiresAt)}));
expect(JSON.parse(res._getData())).toStrictEqual(JSON.parse(JSON.stringify({ data: {...apiKeys} })));
});
});
describe("POST /api/api-keys/ fails, only GET allowed", () => {
it("returns a message with the specified apiKeys", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
});
await handleApiKeys(req, res);
expect(res._getStatusCode()).toBe(405);
expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only GET Method allowed" });
});
});
+71
View File
@@ -0,0 +1,71 @@
import handleNewApiKey from "@api/api-keys/new";
import { createMocks } from "node-mocks-http";
describe("POST /api/api-keys/new with a note", () => {
it("returns a 201, and the created api key", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
body: {
note: "Updated note",
},
});
await handleNewApiKey(req, res);
expect(res._getStatusCode()).toBe(201);
expect(JSON.parse(res._getData()).data.note).toStrictEqual("Updated note");
});
});
describe("POST /api/api-keys/new with a slug param", () => {
it("returns error 400, and the details about invalid slug body param", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
body: {
note: "Updated note",
slug: "slug",
},
});
await handleNewApiKey(req, res);
expect(res._getStatusCode()).toBe(400);
expect(JSON.parse(res._getData())).toStrictEqual(
[{"code": "unrecognized_keys", "keys": ["slug"], "message": "Unrecognized key(s) in object: 'slug'", "path": []}]
);
});
});
describe("GET /api/api-keys/new fails, only POST allowed", () => {
it("returns a message with the specified apiKeys", async () => {
const { req, res } = createMocks({
method: "GET", // This POST method is not allowed
});
await handleNewApiKey(req, res);
expect(res._getStatusCode()).toBe(405);
expect(JSON.parse(res._getData())).toStrictEqual({ error: "Only POST Method allowed" });
});
});
// FIXME: test 405 when prisma fails look for how to test prisma errors
describe("GET /api/api-keys/new fails, only POST allowed", () => {
it("returns a message with the specified apiKeys", async () => {
const { req, res } = createMocks({
method: "POST", // This POST method is not allowed
body: {
nonExistentParam: true
// note: '123',
// slug: 12,
},
});
await handleNewApiKey(req, res);
expect(res._getStatusCode()).toBe(400);
expect(JSON.parse(res._getData())).toStrictEqual([{
"code": "unrecognized_keys",
"keys": ["nonExistentParam"],
"message": "Unrecognized key(s) in object: 'nonExistentParam'", "path": []
}]);
});
});
+2 -1
View File
@@ -21,7 +21,8 @@
"jsx": "preserve",
"paths": {
"@api/*": ["pages/api/*"],
"@lib/*": ["lib/*"]
"@lib/*": ["lib/*"],
"@/*": ["*"]
},
},