Merge pull request #59 from calcom/fix/attendees-id
commit
944e9b181f
|
@ -3,13 +3,21 @@ import { z } from "zod";
|
|||
|
||||
import { _AvailabilityModel as Availability } from "@calcom/prisma/zod";
|
||||
|
||||
export const schemaAvailabilityBaseBodyParams = Availability.omit({ id: true }).partial();
|
||||
export const schemaAvailabilityBaseBodyParams = Availability.pick({
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
date: true,
|
||||
scheduleId: true,
|
||||
days: true,
|
||||
}).partial();
|
||||
|
||||
export const schemaAvailabilityPublic = Availability.omit({});
|
||||
|
||||
const schemaAvailabilityRequiredParams = z.object({
|
||||
startTime: z.string(),
|
||||
endTime: z.string(),
|
||||
startTime: z.date().or(z.string()).optional(),
|
||||
endTime: z.date().or(z.string()).optional(),
|
||||
days: z.array(z.number()).optional(),
|
||||
eventTypeId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const schemaAvailabilityBodyParams = schemaAvailabilityBaseBodyParams.merge(
|
||||
|
|
|
@ -1,36 +1,128 @@
|
|||
import { withValidation } from "next-validations";
|
||||
import * as tzdb from "tzdata";
|
||||
import { z } from "zod";
|
||||
|
||||
import { _UserModel as User } from "@calcom/prisma/zod";
|
||||
|
||||
export const schemaUserBaseBodyParams = User.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
password: true,
|
||||
twoFactorEnabled: true,
|
||||
twoFactorSecret: true,
|
||||
// @note: These are the ONLY values allowed as weekStart. So user don't introduce bad data.
|
||||
enum weekdays {
|
||||
MONDAY = "Monday",
|
||||
TUESDAY = "Tuesday",
|
||||
WEDNESDAY = "Wednesday",
|
||||
THURSDAY = "Thursday",
|
||||
FRIDAY = "Friday",
|
||||
SATURDAY = "Saturday",
|
||||
SUNDAY = "Sunday",
|
||||
}
|
||||
|
||||
// @note: extracted from apps/web/next-i18next.config.js, update if new locales.
|
||||
enum locales {
|
||||
EN = "en",
|
||||
FR = "fr",
|
||||
IT = "it",
|
||||
RU = "ru",
|
||||
ES = "es",
|
||||
DE = "de",
|
||||
PT = "pt",
|
||||
RO = "ro",
|
||||
NL = "nl",
|
||||
PT_BR = "pt-BR",
|
||||
ES_419 = "es-419",
|
||||
KO = "ko",
|
||||
JA = "ja",
|
||||
PL = "pl",
|
||||
AR = "ar",
|
||||
IW = "iw",
|
||||
ZH_CN = "zh-CN",
|
||||
ZH_TW = "zh-TW",
|
||||
CS = "cs",
|
||||
SR = "sr",
|
||||
SV = "sv",
|
||||
VI = "vi",
|
||||
}
|
||||
enum theme {
|
||||
DARK = "dark",
|
||||
LIGHT = "light",
|
||||
}
|
||||
|
||||
enum timeFormat {
|
||||
TWELVE = 12,
|
||||
TWENTY_FOUR = 24,
|
||||
}
|
||||
|
||||
// @note: These are the values that are editable via PATCH method on the user Model
|
||||
export const schemaUserBaseBodyParams = User.pick({
|
||||
name: true,
|
||||
bio: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
endTime: true,
|
||||
bufferTime: true,
|
||||
theme: true,
|
||||
defaultScheduleId: true,
|
||||
locale: true,
|
||||
timeFormat: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
allowDynamicBooking: true,
|
||||
away: true,
|
||||
// @note: disallowing avatar changes via API for now. We can add it later if needed. User should upload image via UI.
|
||||
// avatar: true,
|
||||
}).partial();
|
||||
// @note: partial() is used to allow for the user to edit only the fields they want to edit making all optional,
|
||||
// if want to make any required do it in the schemaRequiredParams
|
||||
|
||||
// Here we can both require or not (adding optional or nullish) and also rewrite validations for any value
|
||||
// for example making weekStart only accept weekdays as input
|
||||
const schemaUserRequiredParams = z.object({
|
||||
email: z.string().email(),
|
||||
weekStart: z.nativeEnum(weekdays).optional(),
|
||||
brandColor: z.string().min(4).max(9).regex(/^#/).optional(),
|
||||
timeZone: z
|
||||
.string()
|
||||
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
|
||||
.refine((tz: string) => Object.keys(tzdb.zones).includes(tz))
|
||||
.optional(),
|
||||
bufferTime: z.number().min(0).max(86400).optional(),
|
||||
startTime: z.number().min(0).max(86400).optional(),
|
||||
endTime: z.number().min(0).max(86400).optional(),
|
||||
theme: z.nativeEnum(theme).optional(),
|
||||
timeFormat: z.nativeEnum(timeFormat).optional(),
|
||||
defaultScheduleId: z
|
||||
.number()
|
||||
.refine((id: number) => id > 0)
|
||||
.optional(),
|
||||
locale: z.nativeEnum(locales),
|
||||
});
|
||||
|
||||
export const schemaUserBodyParams = schemaUserBaseBodyParams.merge(schemaUserRequiredParams);
|
||||
// @note: These are the values that are editable via PATCH method on the user Model,
|
||||
// merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end.
|
||||
export const schemaUserEditBodyParams = schemaUserBaseBodyParams.merge(schemaUserRequiredParams).omit({});
|
||||
|
||||
export const schemaUserPublic = User.omit({
|
||||
identityProvider: true,
|
||||
identityProviderId: true,
|
||||
plan: true,
|
||||
metadata: true,
|
||||
password: true,
|
||||
twoFactorEnabled: true,
|
||||
twoFactorSecret: true,
|
||||
trialEndsAt: true,
|
||||
completedOnboarding: true,
|
||||
// @note: These are the values that are always returned when reading a user
|
||||
export const schemaUserReadPublic = User.pick({
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
endTime: true,
|
||||
bufferTime: true,
|
||||
theme: true,
|
||||
defaultScheduleId: true,
|
||||
locale: true,
|
||||
timeFormat: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
allowDynamicBooking: true,
|
||||
away: true,
|
||||
createdDate: true,
|
||||
verified: true,
|
||||
invitedTo: true,
|
||||
});
|
||||
|
||||
export const withValidUser = withValidation({
|
||||
schema: schemaUserBodyParams,
|
||||
type: "Zod",
|
||||
mode: "body",
|
||||
});
|
||||
// @note: This is the validation for the PATCH method on the user Model. Not used for now.
|
||||
export const withValidUser = withValidation({ schema: schemaUserEditBodyParams, type: "Zod", mode: "body" });
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"next-swagger-doc": "^0.2.1",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"next-validations": "^0.1.11",
|
||||
"typescript": "^4.6.3"
|
||||
"typescript": "^4.6.3",
|
||||
"tzdata": "^1.0.30"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,13 +86,13 @@ import {
|
|||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function attendeeById(req: NextApiRequest, res: NextApiResponse<AttendeeResponse>) {
|
||||
const { method, query, body } = req;
|
||||
const { method, query, body, userId } = req;
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
const safeBody = schemaAttendeeBodyParams.safeParse(body);
|
||||
if (!safeQuery.success) {
|
||||
res.status(400).json({ error: safeQuery.error });
|
||||
throw new Error("Invalid request query", safeQuery.error);
|
||||
}
|
||||
const userId = req.userId;
|
||||
const userBookings = await prisma.booking.findMany({
|
||||
where: { userId },
|
||||
include: { attendees: true },
|
||||
|
@ -118,6 +118,7 @@ export async function attendeeById(req: NextApiRequest, res: NextApiResponse<Att
|
|||
|
||||
case "PATCH":
|
||||
if (!safeBody.success) {
|
||||
res.status(400).json({ message: "Bad request", error: safeBody.error });
|
||||
throw new Error("Invalid request body");
|
||||
}
|
||||
await prisma.attendee
|
||||
|
|
|
@ -93,8 +93,8 @@ import {
|
|||
*/
|
||||
export async function availabilityById(req: NextApiRequest, res: NextApiResponse<AvailabilityResponse>) {
|
||||
const { method, query, body } = req;
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
const safeBody = schemaAvailabilityBodyParams.safeParse(body);
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
if (!safeQuery.success) throw new Error("Invalid request query", safeQuery.error);
|
||||
const userId = req.userId;
|
||||
const data = await prisma.availability.findMany({ where: { userId } });
|
||||
|
@ -113,7 +113,15 @@ export async function availabilityById(req: NextApiRequest, res: NextApiResponse
|
|||
break;
|
||||
|
||||
case "PATCH":
|
||||
|
||||
if (!safeBody.success) throw new Error("Invalid request body");
|
||||
const userEventTypes = await prisma.eventType.findMany({ where: { userId } });
|
||||
const userEventTypesIds = userEventTypes.map((event) => event.id);
|
||||
if (safeBody.data.eventTypeId && !userEventTypesIds.includes(safeBody.data.eventTypeId)) {
|
||||
res.status(401).json({
|
||||
message: `Bad request. You're not the owner of eventTypeId: ${safeBody.data.eventTypeId}`,
|
||||
});
|
||||
}
|
||||
await prisma.availability
|
||||
.update({
|
||||
where: { id: safeQuery.data.id },
|
||||
|
@ -121,9 +129,13 @@ export async function availabilityById(req: NextApiRequest, res: NextApiResponse
|
|||
})
|
||||
.then((data) => schemaAvailabilityPublic.parse(data))
|
||||
.then((availability) => res.status(200).json({ availability }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({ message: `Availability with id: ${safeQuery.data.id} not found`, error })
|
||||
);
|
||||
.catch((error: Error) => {
|
||||
console.log(error);
|
||||
res.status(404).json({
|
||||
message: `Availability with id: ${safeQuery.data.id} not found`,
|
||||
error,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "DELETE":
|
||||
|
|
|
@ -59,10 +59,12 @@ async function createOrlistAllAvailabilities(
|
|||
error,
|
||||
});
|
||||
} else if (method === "POST") {
|
||||
console.log(req.body);
|
||||
const safe = schemaAvailabilityBodyParams.safeParse(req.body);
|
||||
if (!safe.success) throw new Error("Invalid request body");
|
||||
|
||||
const data = await prisma.availability.create({ data: { ...safe.data, userId } });
|
||||
console.log(data);
|
||||
const availability = schemaAvailabilityPublic.parse(data);
|
||||
|
||||
if (availability) res.status(201).json({ availability, message: "Availability created successfully" });
|
||||
|
|
|
@ -70,4 +70,4 @@ async function createOrlistAllSchedules(
|
|||
} else res.status(405).json({ message: `Method ${method} not allowed` });
|
||||
}
|
||||
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(withValidSchedule(createOrlistAllSchedules));
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllSchedules);
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
schemaQueryIdParseInt,
|
||||
withValidQueryIdTransformParseInt,
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
import { schemaUserBodyParams, schemaUserPublic } from "@lib/validations/user";
|
||||
import { schemaUserEditBodyParams, schemaUserReadPublic, withValidUser } from "@lib/validations/user";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
|
@ -39,12 +39,10 @@ import { schemaUserBodyParams, schemaUserPublic } from "@lib/validations/user";
|
|||
* - application/json
|
||||
* parameters:
|
||||
* - in: body
|
||||
* name: user
|
||||
* description: The user to edit
|
||||
* name: name
|
||||
* description: The users full name
|
||||
* schema:
|
||||
* type: object
|
||||
* $ref: '#/components/schemas/User'
|
||||
* required: true
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
|
@ -85,19 +83,18 @@ import { schemaUserBodyParams, schemaUserPublic } from "@lib/validations/user";
|
|||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function userById(req: NextApiRequest, res: NextApiResponse<UserResponse>) {
|
||||
const { method, query, body } = req;
|
||||
export async function userById(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { method, query, body, userId } = req;
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
const safeBody = schemaUserBodyParams.safeParse(body);
|
||||
console.log(body);
|
||||
if (!safeQuery.success) throw new Error("Invalid request query", safeQuery.error);
|
||||
const userId = req.userId;
|
||||
if (safeQuery.data.id !== userId) res.status(401).json({ message: "Unauthorized" });
|
||||
else {
|
||||
switch (method) {
|
||||
case "GET":
|
||||
await prisma.user
|
||||
.findUnique({ where: { id: safeQuery.data.id } })
|
||||
.then((data) => schemaUserPublic.parse(data))
|
||||
.then((data) => schemaUserReadPublic.parse(data))
|
||||
.then((user) => res.status(200).json({ user }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({ message: `User with id: ${safeQuery.data.id} not found`, error })
|
||||
|
@ -105,13 +102,29 @@ export async function userById(req: NextApiRequest, res: NextApiResponse<UserRes
|
|||
break;
|
||||
|
||||
case "PATCH":
|
||||
if (!safeBody.success) throw new Error("Invalid request body");
|
||||
const safeBody = schemaUserEditBodyParams.safeParse(body);
|
||||
if (!safeBody.success) {
|
||||
res.status(400).json({ message: "Bad request", error: safeBody.error });
|
||||
throw new Error("Invalid request body");
|
||||
}
|
||||
const userSchedules = await prisma.schedule.findMany({
|
||||
where: { userId },
|
||||
});
|
||||
const userSchedulesIds = userSchedules.map((schedule) => schedule.id);
|
||||
// @note: here we make sure user can only make as default his own scheudles
|
||||
if (!userSchedulesIds.includes(Number(safeBody?.data?.defaultScheduleId))) {
|
||||
res.status(400).json({
|
||||
message: "Bad request",
|
||||
error: "Invalid default schedule id",
|
||||
});
|
||||
throw new Error("Invalid request body value: defaultScheduleId");
|
||||
}
|
||||
await prisma.user
|
||||
.update({
|
||||
where: { id: safeQuery.data.id },
|
||||
where: { id: userId },
|
||||
data: safeBody.data,
|
||||
})
|
||||
.then((data) => schemaUserPublic.parse(data))
|
||||
.then((data) => schemaUserReadPublic.parse(data))
|
||||
.then((user) => res.status(200).json({ user }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({ message: `User with id: ${safeQuery.data.id} not found`, error })
|
||||
|
|
|
@ -4,7 +4,7 @@ import prisma from "@calcom/prisma";
|
|||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { UsersResponse } from "@lib/types";
|
||||
import { schemaUserPublic } from "@lib/validations/user";
|
||||
import { schemaUserReadPublic } from "@lib/validations/user";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
|
@ -30,7 +30,7 @@ async function allUsers(req: NextApiRequest, res: NextApiResponse<UsersResponse>
|
|||
id: userId,
|
||||
},
|
||||
});
|
||||
const users = data.map((user) => schemaUserPublic.parse(user));
|
||||
const users = data.map((user) => schemaUserReadPublic.parse(user));
|
||||
if (users) res.status(200).json({ users });
|
||||
else
|
||||
(error: Error) =>
|
||||
|
|
Loading…
Reference in New Issue