Refactor/custom inputs (#184)

refs #175 

To be merged after #183
This commit is contained in:
Omar López
2022-10-13 12:30:48 -06:00
committed by GitHub
parent 8c24c5c714
commit 6ba70a7259
10 changed files with 241 additions and 258 deletions
+5 -19
View File
@@ -1,27 +1,13 @@
import { z } from "zod";
import { _EventTypeCustomInputModel as EventTypeCustomInput } from "@calcom/prisma/zod";
export const schemaEventTypeCustomInputBaseBodyParams = EventTypeCustomInput.omit({
id: true,
eventTypeId: true,
}).partial();
});
export const schemaEventTypeCustomInputPublic = EventTypeCustomInput.omit({});
const schemaEventTypeCustomInputRequiredParams = z.object({
label: z.string(),
required: z.boolean(),
type: z.enum(["TEXT", "TEXTLONG", "NUMBER", "BOOL"]),
eventType: z.object({
connect: z.object({
id: z.number().optional(),
}),
// FIXME: Provide valid EventTypeModel schema here, but not sure how yet.
create: z.any(),
}),
});
export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.strict();
export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.merge(
schemaEventTypeCustomInputRequiredParams
);
export const schemaEventTypeCustomInputEditBodyParams = schemaEventTypeCustomInputBaseBodyParams
.partial()
.strict();
-151
View File
@@ -1,151 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import type { EventTypeCustomInputResponse } from "@lib/types";
import {
schemaEventTypeCustomInputBodyParams,
schemaEventTypeCustomInputPublic,
} from "@lib/validations/event-type-custom-input";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /custom-inputs/{id}:
* get:
* summary: Find a eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to get
* tags:
* - custom-inputs
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: EventType was not found
* patch:
* summary: Edit an existing eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to edit
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput edited successfuly
* 400:
* description: Bad request. EventType body is invalid.
* 401:
* description: Authorization information is missing or invalid.
* delete:
* summary: Remove an existing eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to delete
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput removed successfuly
* 400:
* description: Bad request. EventType id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
async function eventTypeById(
{ method, query, body, userId, prisma }: NextApiRequest,
res: NextApiResponse<EventTypeCustomInputResponse>
) {
const safeQuery = schemaQueryIdParseInt.safeParse(query);
const safeBody = schemaEventTypeCustomInputBodyParams.safeParse(body);
if (!safeQuery.success) {
res.status(400).json({ message: "Your query was invalid" });
return;
}
const data = await prisma.eventType.findMany({ where: { userId } });
const userEventTypes = data.map((eventType) => eventType.id);
const userEventTypeCustomInputs = await prisma.eventTypeCustomInput.findMany({
where: { eventType: userEventTypes },
});
const userEventTypeCustomInputIds = userEventTypeCustomInputs.map(
(eventTypeCustomInput) => eventTypeCustomInput.id
);
if (!userEventTypeCustomInputIds.includes(safeQuery.data.id))
res.status(401).json({ message: "Unauthorized" });
else {
switch (method) {
case "GET":
await prisma.eventTypeCustomInput
.findUnique({ where: { id: safeQuery.data.id } })
.then((data) => schemaEventTypeCustomInputPublic.parse(data))
.then((event_type_custom_input) => res.status(200).json({ event_type_custom_input }))
.catch((error: Error) =>
res.status(404).json({
message: `EventType with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
case "PATCH":
if (!safeBody.success) {
{
res.status(400).json({ message: "Invalid request body" });
return;
}
}
await prisma.eventTypeCustomInput
.update({ where: { id: safeQuery.data.id }, data: safeBody.data })
.then((data) => schemaEventTypeCustomInputPublic.parse(data))
.then((event_type_custom_input) => res.status(200).json({ event_type_custom_input }))
.catch((error: Error) =>
res.status(404).json({
message: `EventType with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
case "DELETE":
await prisma.eventTypeCustomInput
.delete({
where: { id: safeQuery.data.id },
})
.then(() =>
res.status(200).json({
message: `CustomInputEventType with id: ${safeQuery.data.id} deleted`,
})
)
.catch((error: Error) =>
res.status(404).json({
message: `EventType 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(eventTypeById));
@@ -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);
// Admins can just skip this check
if (isAdmin) return;
// Check if the current user can access the event type of this input
const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({
where: { id, eventType: { userId } },
});
if (!eventTypeCustomInput) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
}
export default authMiddleware;
+36
View File
@@ -0,0 +1,36 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /custom-inputs/{id}:
* delete:
* summary: Remove an existing eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to delete
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput removed successfully
* 400:
* description: Bad request. EventType 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.eventTypeCustomInput.delete({ where: { id } });
return { message: `CustomInputEventType with id: ${id} deleted successfully` };
}
export default defaultResponder(deleteHandler);
+37
View File
@@ -0,0 +1,37 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeCustomInputPublic } from "@lib/validations/event-type-custom-input";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /custom-inputs/{id}:
* get:
* summary: Find a eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to get
* tags:
* - custom-inputs
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: EventType was not found
*/
export async function getHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const data = await prisma.eventTypeCustomInput.findUniqueOrThrow({ where: { id } });
return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data) };
}
export default defaultResponder(getHandler);
+41
View File
@@ -0,0 +1,41 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import {
schemaEventTypeCustomInputEditBodyParams,
schemaEventTypeCustomInputPublic,
} from "@lib/validations/event-type-custom-input";
import { schemaQueryIdParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /custom-inputs/{id}:
* patch:
* summary: Edit an existing eventTypeCustomInput
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the eventTypeCustomInput to edit
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput edited successfully
* 400:
* description: Bad request. EventType body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
export async function patchHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const data = schemaEventTypeCustomInputEditBodyParams.parse(req.body);
const result = await prisma.eventTypeCustomInput.update({ where: { id }, data });
return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(result) };
}
export default defaultResponder(patchHandler);
+18
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);
})
);
+30
View File
@@ -0,0 +1,30 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeCustomInputPublic } from "@lib/validations/event-type-custom-input";
/**
* @swagger
* /custom-inputs:
* get:
* summary: Find all eventTypeCustomInputs
* tags:
* - custom-inputs
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No eventTypeCustomInputs were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const args: Prisma.EventTypeCustomInputFindManyArgs = isAdmin ? {} : { where: { eventType: { userId } } };
const data = await prisma.eventTypeCustomInput.findMany(args);
return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) };
}
export default defaultResponder(getHandler);
+48
View File
@@ -0,0 +1,48 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import {
schemaEventTypeCustomInputBodyParams,
schemaEventTypeCustomInputPublic,
} from "@lib/validations/event-type-custom-input";
/**
* @swagger
* /custom-inputs:
* post:
* summary: Creates a new eventTypeCustomInput
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput created
* 400:
* description: Bad request. EventTypeCustomInput body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const { eventTypeId, ...body } = schemaEventTypeCustomInputBodyParams.parse(req.body);
if (!isAdmin) {
/* We check that the user has access to the event type he's trying to add a custom input to. */
const eventType = await prisma.eventType.findFirst({
where: { id: eventTypeId, userId },
});
if (!eventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
}
const data = await prisma.eventTypeCustomInput.create({
data: { ...body, eventType: { connect: { id: eventTypeId } } },
});
return {
event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data),
message: "EventTypeCustomInput created successfully",
};
}
export default defaultResponder(postHandler);
+7 -88
View File
@@ -1,91 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler } from "@calcom/lib/server";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import { EventTypeCustomInputResponse, EventTypeCustomInputsResponse } from "@lib/types";
import {
schemaEventTypeCustomInputBodyParams,
schemaEventTypeCustomInputPublic,
} from "@lib/validations/event-type-custom-input";
async function createOrlistAllEventTypeCustomInputs(
{ userId, method, body, prisma }: NextApiRequest,
res: NextApiResponse<EventTypeCustomInputsResponse | EventTypeCustomInputResponse>
) {
const data = await prisma.eventType.findMany({ where: { userId } });
const userEventTypes: number[] = data.map((eventType) => eventType.id);
if (method === "GET") {
/**
* @swagger
* /custom-inputs:
* get:
* summary: Find all eventTypeCustomInputs
* tags:
* - custom-inputs
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No eventTypeCustomInputs were found
*/
const data = await prisma.eventTypeCustomInput.findMany({ where: { eventType: userEventTypes } });
const event_type_custom_inputs = data.map((eventTypeCustomInput) =>
schemaEventTypeCustomInputPublic.parse(eventTypeCustomInput)
);
if (event_type_custom_inputs) res.status(200).json({ event_type_custom_inputs });
else
(error: Error) =>
res.status(404).json({
message: "No EventTypeCustomInputs were found",
error,
});
} else if (method === "POST") {
/**
* @swagger
* /custom-inputs:
* post:
* summary: Creates a new eventTypeCustomInput
* tags:
* - custom-inputs
* responses:
* 201:
* description: OK, eventTypeCustomInput created
* 400:
* description: Bad request. EventTypeCustomInput body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
const safe = schemaEventTypeCustomInputBodyParams.safeParse(body);
if (!safe.success) {
res.status(400).json({ message: "Invalid request body" });
return;
}
// Since we're supporting a create or connect relation on eventType, we need to treat them differently
// When using connect on event type, check if userId is the owner of the event
if (safe.data.eventType.connect && !userEventTypes.includes(safe.data.eventType.connect.id as number)) {
const data = await prisma.eventTypeCustomInput.create({ data: { ...safe.data } });
const event_type_custom_input = schemaEventTypeCustomInputPublic.parse(data);
if (event_type_custom_input)
res
.status(201)
.json({ event_type_custom_input, message: "EventTypeCustomInput created successfully" });
// When creating, no need
// FIXME: we might want to pass userId to the new created/linked eventType, though.
} else if (safe.data.eventType.create) {
const data = await prisma.eventTypeCustomInput.create({ data: { ...safe.data } });
const event_type_custom_input = schemaEventTypeCustomInputPublic.parse(data);
if (event_type_custom_input)
res
.status(201)
.json({ event_type_custom_input, message: "EventTypeCustomInput created successfully" });
} else
(error: Error) =>
res.status(400).json({
message: "Could not create new eventTypeCustomInput",
error,
});
} else res.status(405).json({ message: `Method ${method} not allowed` });
}
export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllEventTypeCustomInputs);
export default withMiddleware("HTTP_GET_OR_POST")(
defaultHandler({
GET: import("./_get"),
POST: import("./_post"),
})
);