feat: api return schedule (#10769)

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Alex van Andel
2023-08-15 19:18:25 +00:00
committed by GitHub
co-authored by Leo Giovanetti Omar López
parent 98799b50f0
commit 3cab37ed8d
4 changed files with 112 additions and 22 deletions
+1
View File
@@ -98,6 +98,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
position: true,
userId: true,
teamId: true,
scheduleId: true,
eventName: true,
timeZone: true,
periodType: true,
+39 -3
View File
@@ -1,9 +1,11 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
import { checkPermissions as canAccessTeamEventOrThrow } from "~/pages/api/teams/[teamId]/_auth-middleware";
import getCalLink from "../_utils/getCalLink";
@@ -42,7 +44,8 @@ import getCalLink from "../_utils/getCalLink";
export async function getHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const event_type = await prisma.eventType.findUnique({
const eventType = await prisma.eventType.findUnique({
where: { id },
include: {
customInputs: true,
@@ -51,10 +54,43 @@ export async function getHandler(req: NextApiRequest) {
owner: { select: { username: true, id: true } },
},
});
await checkPermissions(req, eventType);
const link = event_type ? getCalLink(event_type) : null;
const link = eventType ? getCalLink(eventType) : null;
// user.defaultScheduleId doesn't work the same for team events.
if (!eventType?.scheduleId && eventType?.userId && !eventType?.teamId) {
const user = await prisma.user.findUniqueOrThrow({
where: {
id: eventType.userId,
},
select: {
defaultScheduleId: true,
},
});
eventType.scheduleId = user.defaultScheduleId;
}
return { event_type: schemaEventTypeReadPublic.parse({ ...event_type, link }) };
// TODO: eventType when not found should be a 404
// but API consumers may depend on the {} behaviour.
return { event_type: schemaEventTypeReadPublic.parse({ ...eventType, link }) };
}
type BaseEventTypeCheckPermissions = {
userId: number | null;
teamId: number | null;
};
async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
req: NextApiRequest,
eventType: (T & Partial<Omit<T, keyof BaseEventTypeCheckPermissions>>) | null
) {
if (req.isAdmin) return true;
if (eventType?.teamId) {
req.query.teamId = String(eventType.teamId);
await canAccessTeamEventOrThrow(req, "MEMBER");
}
if (eventType?.userId === req.userId) return true; // is owner.
throw new HttpError({ statusCode: 403, message: "Forbidden" });
}
export default defaultResponder(getHandler);
+71 -18
View File
@@ -1,8 +1,8 @@
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 type { PrismaClient } from "@calcom/prisma/client";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
@@ -35,20 +35,12 @@ import getCalLink from "./_utils/getCalLink";
* description: No event types were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const args: Prisma.EventTypeFindManyArgs = {
where: { userId },
};
/** Only admins can query other users */
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 401, message: "ADMIN required" });
if (isAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
args.where = { userId: { in: userIds } };
}
const { userId, prisma } = req;
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
const data = await prisma.eventType.findMany({
...args,
where: {
userId: { in: userIds },
},
include: {
customInputs: true,
team: { select: { slug: true } },
@@ -56,13 +48,74 @@ async function getHandler(req: NextApiRequest) {
owner: { select: { username: true, id: true } },
},
});
// this really should return [], but backwards compatibility..
if (data.length === 0) new HttpError({ statusCode: 404, message: "No event types were found" });
return {
event_types: data.map((eventType) => {
const link = getCalLink(eventType);
return schemaEventTypeReadPublic.parse({ ...eventType, link });
}),
event_types: (await defaultScheduleId<(typeof data)[number]>({ eventTypes: data, prisma, userIds })).map(
(eventType) => {
const link = getCalLink(eventType);
return schemaEventTypeReadPublic.parse({ ...eventType, link });
}
),
};
}
// TODO: Extract & reuse.
function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
/** Guard: Only admins can query other users */
if (!isAdmin) {
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
}
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
return Array.isArray(userIdOrUserIds) ? userIdOrUserIds : [userIdOrUserIds];
}
type DefaultScheduleIdEventTypeBase = {
scheduleId: number | null;
userId: number | null;
};
// If an eventType is given w/o a scheduleId
// Then we associate the default user schedule id to the eventType
async function defaultScheduleId<T extends DefaultScheduleIdEventTypeBase>({
prisma,
eventTypes,
userIds,
}: {
prisma: PrismaClient;
eventTypes: (T & Partial<Omit<T, keyof DefaultScheduleIdEventTypeBase>>)[];
userIds: number[];
}) {
// there is no event types without a scheduleId, skip the user query
if (eventTypes.every((eventType) => eventType.scheduleId)) return eventTypes;
const users = await prisma.user.findMany({
where: {
id: {
in: userIds,
},
},
select: {
id: true,
defaultScheduleId: true,
},
});
if (!users.length) {
return eventTypes;
}
const defaultScheduleIds = users.reduce((result, user) => {
result[user.id] = user.defaultScheduleId;
return result;
}, {} as { [x: number]: number | null });
return eventTypes.map((eventType) => {
// realistically never happens, userId should't be null on personal event types.
if (!eventType.userId) return eventType;
return {
...eventType,
scheduleId: eventType.scheduleId || defaultScheduleIds[eventType.userId],
};
});
}
export default defaultResponder(getHandler);
@@ -59,7 +59,7 @@ async function getHandler(req: NextApiRequest) {
};
const data = await prisma.eventType.findMany(args);
return { event_types: data.map((attendee) => schemaEventTypeReadPublic.parse(attendee)) };
return { event_types: data.map((eventType) => schemaEventTypeReadPublic.parse(eventType)) };
}
export default defaultResponder(getHandler);