Refactors booking references endpoints (#180)

refs #175
pull/9078/head
Omar López 2022-10-11 08:25:57 -06:00 committed by GitHub
parent 88332fb2ab
commit da61841525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 303 additions and 328 deletions

View File

@ -1,6 +1,5 @@
import { z } from "zod";
import { _BookingReferenceModel as BookingReference } from "@calcom/prisma/zod";
import { denullishShape } from "@calcom/prisma/zod-utils";
export const schemaBookingReferenceBaseBodyParams = BookingReference.pick({
type: true,
@ -23,31 +22,7 @@ export const schemaBookingReferenceReadPublic = BookingReference.pick({
deleted: true,
});
const schemaBookingReferenceCreateParams = z
.object({
type: z.string(),
uid: z.string(),
meetingId: z.string(),
meetingPassword: z.string().optional(),
meetingUrl: z.string().optional(),
deleted: z.boolean(),
})
export const schemaBookingCreateBodyParams = BookingReference.omit({ id: true, bookingId: true })
.merge(denullishShape(BookingReference.pick({ bookingId: true })))
.strict();
const schemaBookingReferenceEditParams = z
.object({
type: z.string().optional(),
uid: z.string().optional(),
meetingId: z.string().optional(),
meetingPassword: z.string().optional(),
meetingUrl: z.string().optional(),
deleted: z.boolean().optional(),
})
.strict();
export const schemaBookingCreateBodyParams = schemaBookingReferenceBaseBodyParams.merge(
schemaBookingReferenceCreateParams
);
export const schemaBookingEditBodyParams = schemaBookingReferenceBaseBodyParams.merge(
schemaBookingReferenceEditParams
);
export const schemaBookingEditBodyParams = schemaBookingCreateBodyParams.partial();

View File

@ -1,181 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { BookingReferenceResponse } from "@lib/types";
import {
schemaBookingEditBodyParams,
schemaBookingReferenceReadPublic,
} from "@lib/validations/booking-reference";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "@lib/validations/shared/queryIdTransformParseInt";
export async function bookingReferenceById(
{ method, query, body, userId, prisma }: NextApiRequest,
res: NextApiResponse<BookingReferenceResponse>
) {
const safeQuery = schemaQueryIdParseInt.safeParse(query);
if (!safeQuery.success) {
res.status(400).json({ message: "Your query was invalid" });
return;
}
const userWithBookings = await prisma.user.findUnique({
where: { id: userId },
include: { bookings: true },
});
if (!userWithBookings) throw new Error("User not found");
const userBookingIds = userWithBookings.bookings.map((booking: { id: number }) => booking.id).flat();
const bookingReferences = await prisma.bookingReference
.findMany({ where: { id: { in: userBookingIds } } })
.then((bookingReferences) => bookingReferences.map((bookingReference) => bookingReference.id));
if (!bookingReferences?.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" });
else {
switch (method) {
case "GET":
/**
* @swagger
* /booking-references/{id}:
* get:
* operationId: getBookingReferenceById
* summary: Find a booking reference
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to get
* tags:
* - booking-references
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: BookingReference was not found
*/
await prisma.bookingReference
.findFirst({ where: { id: safeQuery.data.id } })
.then((data) => schemaBookingReferenceReadPublic.parse(data))
.then((booking_reference) => res.status(200).json({ booking_reference }))
.catch((error: Error) => {
res.status(404).json({
message: `BookingReference with id: ${safeQuery.data.id} not found`,
error,
});
});
break;
case "PATCH":
/**
* @swagger
* /booking-references/{id}:
* patch:
* operationId: editBookingReferenceById
* summary: Edit an existing booking reference
* requestBody:
* description: Edit an existing booking reference related to one of your bookings
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* days:
* type: array
* example: email@example.com
* startTime:
* type: string
* example: 1970-01-01T17:00:00.000Z
* endTime:
* type: string
* example: 1970-01-01T17:00:00.000Z
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to edit
* tags:
* - booking-references
* responses:
* 201:
* description: OK, safeBody.data edited successfuly
* 400:
* description: Bad request. BookingReference body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
const safeBody = schemaBookingEditBodyParams.safeParse(body);
if (!safeBody.success) {
console.log(safeBody.error);
res.status(400).json({ message: "Invalid request body", error: safeBody.error });
return;
}
await prisma.bookingReference
.update({ where: { id: safeQuery.data.id }, data: safeBody.data })
.then((data) => schemaBookingReferenceReadPublic.parse(data))
.then((booking_reference) => res.status(200).json({ booking_reference }))
.catch((error: Error) =>
res.status(404).json({
message: `BookingReference with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
case "DELETE":
/**
* @swagger
* /booking-references/{id}:
* delete:
* operationId: removeBookingReferenceById
* summary: Remove an existing booking reference
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to delete
* tags:
* - booking-references
* responses:
* 201:
* description: OK, bookingReference removed successfuly
* 400:
* description: Bad request. BookingReference id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
await prisma.bookingReference
.delete({
where: { id: safeQuery.data.id },
})
.then(() =>
res.status(200).json({
message: `BookingReference with id: ${safeQuery.data.id} deleted`,
})
)
.catch((error: Error) =>
res.status(404).json({
message: `BookingReference with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
default:
res.status(405).json({ message: "Method not allowed" });
break;
}
}
}
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
withValidQueryIdTransformParseInt(bookingReferenceById)
);

View File

@ -0,0 +1,19 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
// Here we make sure to only return references of the user's own bookings if the user is not an admin.
if (isAdmin) return;
// Find all references where the user has bookings
const bookingReference = await prisma.bookingReference.findFirst({
where: { id, booking: { userId } },
});
if (!bookingReference) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
}
export default authMiddleware;

View File

@ -0,0 +1,37 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /booking-references/{id}:
* delete:
* operationId: removeBookingReferenceById
* summary: Remove an existing booking reference
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to delete
* tags:
* - booking-references
* responses:
* 201:
* description: OK, bookingReference removed successfully
* 400:
* description: Bad request. BookingReference id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
export async function deleteHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
await prisma.bookingReference.delete({ where: { id } });
return { message: `BookingReference with id: ${id} deleted` };
}
export default defaultResponder(deleteHandler);

View File

@ -0,0 +1,38 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaBookingReferenceReadPublic } from "@lib/validations/booking-reference";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /booking-references/{id}:
* get:
* operationId: getBookingReferenceById
* summary: Find a booking reference
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to get
* tags:
* - booking-references
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: BookingReference was not found
*/
export async function getHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const booking_reference = await prisma.bookingReference.findUniqueOrThrow({ where: { id } });
return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) };
}
export default defaultResponder(getHandler);

View File

@ -0,0 +1,69 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import {
schemaBookingEditBodyParams,
schemaBookingReferenceReadPublic,
} from "@lib/validations/booking-reference";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /booking-references/{id}:
* patch:
* operationId: editBookingReferenceById
* summary: Edit an existing booking reference
* requestBody:
* description: Edit an existing booking reference related to one of your bookings
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* days:
* type: array
* example: email@example.com
* startTime:
* type: string
* example: 1970-01-01T17:00:00.000Z
* endTime:
* type: string
* example: 1970-01-01T17:00:00.000Z
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the booking reference to edit
* tags:
* - booking-references
* responses:
* 201:
* description: OK, safeBody.data edited successfully
* 400:
* description: Bad request. BookingReference body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
export async function patchHandler(req: NextApiRequest) {
const { prisma, query, body, isAdmin, userId } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const data = schemaBookingEditBodyParams.parse(body);
/* If user tries to update bookingId, we run extra checks */
if (data.bookingId) {
const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin
? /* If admin, we only check that the booking exists */
{ where: { id: data.bookingId } }
: /* For non-admins we make sure the booking belongs to the user */
{ where: { id: data.bookingId, userId } };
await prisma.booking.findFirstOrThrow(args);
}
const booking_reference = await prisma.bookingReference.update({ where: { id }, data });
return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) };
}
export default defaultResponder(patchHandler);

View File

@ -0,0 +1,18 @@
import { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import authMiddleware from "./_auth-middleware";
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
await authMiddleware(req);
return defaultHandler({
GET: import("./_get"),
PATCH: import("./_patch"),
DELETE: import("./_delete"),
})(req, res);
})
);

View File

@ -0,0 +1,31 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaBookingReferenceReadPublic } from "@lib/validations/booking-reference";
/**
* @swagger
* /booking-references:
* get:
* operationId: listBookingReferences
* summary: Find all booking references
* tags:
* - booking-references
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No booking references were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const args: Prisma.BookingReferenceFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } };
const data = await prisma.bookingReference.findMany(args);
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
}
export default defaultResponder(getHandler);

View File

@ -0,0 +1,80 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import {
schemaBookingCreateBodyParams,
schemaBookingReferenceReadPublic,
} from "@lib/validations/booking-reference";
/**
* @swagger
* /booking-references:
* post:
* operationId: addBookingReference
* summary: Creates a new booking reference
* requestBody:
* description: Create a new booking reference related to one of your bookings
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - type
* - uid
* - meetingId
* - bookingId
* - deleted
* properties:
* deleted:
* type: boolean
* example: false
* uid:
* type: string
* example: '123456789'
* type:
* type: string
* example: email@example.com
* bookingId:
* type: number
* example: 1
* meetingId:
* type: string
* example: 'meeting-id'
* tags:
* - booking-references
* responses:
* 201:
* description: OK, booking reference created
* 400:
* description: Bad request. BookingReference body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const body = schemaBookingCreateBodyParams.parse(req.body);
const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin
? /* If admin, we only check that the booking exists */
{ where: { id: body.bookingId } }
: /* For non-admins we make sure the booking belongs to the user */
{ where: { id: body.bookingId, userId } };
await prisma.booking.findFirstOrThrow(args);
const data = await prisma.bookingReference.create({
data: {
...body,
bookingId: body.bookingId,
},
});
return {
booking_reference: schemaBookingReferenceReadPublic.parse(data),
message: "Booking reference created successfully",
};
}
export default defaultResponder(postHandler);

View File

@ -1,121 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler } from "@calcom/lib/server";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import { BookingReferenceResponse, BookingReferencesResponse } from "@lib/types";
import {
schemaBookingCreateBodyParams,
schemaBookingReferenceReadPublic,
} from "@lib/validations/booking-reference";
async function createOrlistAllBookingReferences(
{ method, userId, body, prisma }: NextApiRequest,
res: NextApiResponse<BookingReferencesResponse | BookingReferenceResponse>
) {
const userWithBookings = await prisma.user.findUnique({
where: { id: userId },
include: { bookings: true },
});
if (!userWithBookings) throw new Error("User not found");
const userBookingIds = userWithBookings.bookings.map((booking: { id: number }) => booking.id).flat();
if (method === "GET") {
/**
* @swagger
* /booking-references:
* get:
* operationId: listBookingReferences
* summary: Find all booking references
* tags:
* - booking-references
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No booking references were found
*/
const data = await prisma.bookingReference.findMany({ where: { id: { in: userBookingIds } } });
const booking_references = data.map((bookingReference) =>
schemaBookingReferenceReadPublic.parse(bookingReference)
);
if (booking_references) res.status(200).json({ booking_references });
else
(error: Error) =>
res.status(404).json({
message: "No BookingReferences were found",
error,
});
} else if (method === "POST") {
/**
* @swagger
* /booking-references:
* post:
* operationId: addBookingReference
* summary: Creates a new booking reference
* requestBody:
* description: Create a new booking reference related to one of your bookings
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - type
* - uid
* - meetindId
* - bookingId
* - deleted
* properties:
* deleted:
* type: boolean
* example: false
* uid:
* type: string
* example: '123456789'
* type:
* type: string
* example: email@example.com
* bookingId:
* type: number
* example: 1
* meetingId:
* type: string
* example: 'meeting-id'
* tags:
* - booking-references
* responses:
* 201:
* description: OK, booking reference created
* 400:
* description: Bad request. BookingReference body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
const safe = schemaBookingCreateBodyParams.safeParse(body);
if (!safe.success) {
res.status(400).json({ message: "Bad request. BookingReference body is invalid", error: safe.error });
return;
}
if (!safe.data.bookingId) throw new Error("BookingReference: bookingId not found");
if (!userBookingIds.includes(safe.data.bookingId)) res.status(401).json({ message: "Unauthorized" });
else {
const booking_reference = await prisma.bookingReference.create({
data: { ...safe.data },
});
if (booking_reference) {
res.status(201).json({
booking_reference,
message: "BookingReference created successfully",
});
} else {
(error: Error) =>
res.status(400).json({
message: "Could not create new booking reference",
error,
});
}
}
} else res.status(405).json({ message: `Method ${method} not allowed` });
}
export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllBookingReferences);
export default withMiddleware("HTTP_GET_OR_POST")(
defaultHandler({
GET: import("./_get"),
POST: import("./_post"),
})
);