feat: add parentid eventtype API along with children eventTypeIds (#11714)

* add parentId

* Allow POST parentId

* adds eventtypeid of children

* add doc

* adds userId in children

* adds validations

* adds docstring

* fix status codes

* check fix for owners
This commit is contained in:
Syed Ali Shahbaz 2023-10-06 17:58:51 +04:00 committed by GitHub
parent ceb8906c27
commit 49b0f48be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 2 deletions

View File

@ -24,6 +24,11 @@ const hostSchema = _HostModel.pick({
userId: true,
});
export const childrenSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
});
export const schemaEventTypeBaseBodyParams = EventType.pick({
title: true,
description: true,
@ -45,6 +50,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
disableGuests: true,
hideCalendarNotes: true,
minimumBookingNotice: true,
parentId: true,
beforeEventBuffer: true,
afterEventBuffer: true,
teamId: true,
@ -56,7 +62,12 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
bookingLimits: true,
durationLimits: true,
})
.merge(z.object({ hosts: z.array(hostSchema).optional().default([]) }))
.merge(
z.object({
children: z.array(childrenSchema).optional().default([]),
hosts: z.array(hostSchema).optional().default([]),
})
)
.partial()
.strict();
@ -73,6 +84,7 @@ const schemaEventTypeCreateParams = z
seatsShowAvailabilityCount: z.boolean().optional(),
bookingFields: eventTypeBookingFields.optional(),
scheduleId: z.number().optional(),
parentId: z.number().optional(),
})
.strict();
@ -125,6 +137,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
price: true,
currency: true,
slotInterval: true,
parentId: true,
successRedirectUrl: true,
description: true,
locations: true,
@ -137,6 +150,8 @@ export const schemaEventTypeReadPublic = EventType.pick({
durationLimits: true,
}).merge(
z.object({
children: z.array(childrenSchema).optional().default([]),
hosts: z.array(hostSchema).optional().default([]),
locations: z
.array(
z.object({

View File

@ -52,6 +52,7 @@ export async function getHandler(req: NextApiRequest) {
team: { select: { slug: true } },
users: true,
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
});
await checkPermissions(req, eventType);

View File

@ -46,6 +46,7 @@ async function getHandler(req: NextApiRequest) {
team: { select: { slug: true } },
users: true,
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
});
// this really should return [], but backwards compatibility..

View File

@ -6,7 +6,9 @@ import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import checkParentEventOwnership from "./_utils/checkParentEventOwnership";
import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission";
import checkUserMembership from "./_utils/checkUserMembership";
import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
/**
@ -118,10 +120,13 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* schedulingType:
* type: string
* description: The type of scheduling if a Team event. Required for team events only
* enum: [ROUND_ROBIN, COLLECTIVE]
* enum: [ROUND_ROBIN, COLLECTIVE, MANAGED]
* price:
* type: integer
* description: Price of the event type booking
* parentId:
* type: integer
* description: EventTypeId of the parent managed event
* currency:
* type: string
* description: Currency acronym. Eg- usd, eur, gbp, etc.
@ -276,6 +281,11 @@ async function postHandler(req: NextApiRequest) {
await checkPermissions(req);
if (parsedBody.parentId) {
await checkParentEventOwnership(parsedBody.parentId, userId);
await checkUserMembership(parsedBody.parentId, parsedBody.userId);
}
if (isAdmin && parsedBody.userId) {
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
}

View File

@ -0,0 +1,52 @@
import { HttpError } from "@calcom/lib/http-error";
/**
* Checks if a user, identified by the provided userId, has ownership (or admin rights) over
* the team associated with the event type identified by the parentId.
*
* @param parentId - The ID of the parent event type.
* @param userId - The ID of the user.
*
* @throws {HttpError} If the parent event type is not found,
* if the parent event type doesn't belong to any team,
* or if the user doesn't have ownership or admin rights to the associated team.
*/
export default async function checkParentEventOwnership(parentId: number, userId: number) {
const parentEventType = await prisma.eventType.findUnique({
where: {
id: parentId,
},
select: {
teamId: true,
},
});
if (!parentEventType) {
throw new HttpError({
statusCode: 404,
message: "Parent event type not found.",
});
}
if (!parentEventType.teamId) {
throw new HttpError({
statusCode: 400,
message: "This event type is not capable of having children",
});
}
const teamMember = await prisma.membership.findFirst({
where: {
teamId: parentEventType.teamId,
userId: userId,
OR: [{ role: "OWNER" }, { role: "ADMIN" }],
},
});
if (!teamMember) {
throw new HttpError({
statusCode: 403,
message: "User is not authorized to access the team to which the parent event type belongs.",
});
}
}

View File

@ -0,0 +1,52 @@
import { HttpError } from "@calcom/lib/http-error";
/**
* Checks if a user, identified by the provided userId, is a member of the team associated
* with the event type identified by the parentId.
*
* @param parentId - The ID of the event type.
* @param userId - The ID of the user.
*
* @throws {HttpError} If the event type is not found,
* if the event type doesn't belong to any team,
* or if the user isn't a member of the associated team.
*/
export default async function checkUserMembership(parentId: number, userId: number) {
const parentEventType = await prisma.eventType.findUnique({
where: {
id: parentId,
},
select: {
teamId: true,
},
});
if (!parentEventType) {
throw new HttpError({
statusCode: 404,
message: "Event type not found.",
});
}
if (!parentEventType.teamId) {
throw new HttpError({
statusCode: 400,
message: "This event type is not capable of having children.",
});
}
const teamMember = await prisma.membership.findFirst({
where: {
teamId: parentEventType.teamId,
userId: userId,
accepted: true,
},
});
if (!teamMember) {
throw new HttpError({
statusCode: 400,
message: "User is not a team member.",
});
}
}