fix: merge w main
This commit is contained in:
@@ -187,4 +187,19 @@ Add a deployment or go to an existing one.
|
||||
Activate API or Admin addon
|
||||
Provide your DATABASE-URL
|
||||
Store it safely, you'll get a customApiID, save it.
|
||||
call api.cal.com?apiKey=your_cal_instance_apiKey&customApiId=cal_datasource_key
|
||||
call api.cal.com?apiKey=your_cal_instance_apiKey&customApiId=cal_datasource_key
|
||||
## How to deploy
|
||||
|
||||
We recommend deploying API in vercel.
|
||||
|
||||
There's some settings that you'll need to setup.
|
||||
|
||||
Under Vercel > Your API Deployment > Settings
|
||||
|
||||
In General > Build & Development Settings
|
||||
BUILD COMMAND: `yarn turbo run build --scope=@calcom/api --include-dependencies --no-deps`
|
||||
OUTPUT DIRECTORY: `apps/api/.next`
|
||||
|
||||
See `scripts/vercel-deploy.sh` for more info on how the deployment is done.
|
||||
## Environment variables
|
||||
Lastly API requires an env var for `DATABASE_URL`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IncomingMessage } from "http";
|
||||
import { NextMiddleware } from "next-api-middleware";
|
||||
|
||||
import type { PrismaClient } from ".prisma/client";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
|
||||
/** @todo figure how to use the one from `@calcom/types` */
|
||||
/** @todo: remove once `@calcom/types` is updated with it.*/
|
||||
@@ -14,8 +14,10 @@ declare module "next" {
|
||||
prisma: PrismaClient;
|
||||
session: { user: { id: number } };
|
||||
query: { [key: string]: string | string[] };
|
||||
isAdmin: boolean;
|
||||
}
|
||||
}
|
||||
export const extendRequest: NextMiddleware = async (req, res, next) => {
|
||||
// @note: just an empty middleware, we care about extending the next api request types only.
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// import type { IncomingMessage } from "http";
|
||||
import { NextMiddleware } from "next-api-middleware";
|
||||
|
||||
import { hashAPIKey } from "@calcom/ee/lib/api/apiKeys";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
|
||||
// Used to check if the apiKey is not expired, could be extracted if reused. but not for now.
|
||||
export const dateNotInPast = function (date: Date) {
|
||||
const now = new Date();
|
||||
@@ -15,15 +16,12 @@ export const dateNotInPast = function (date: Date) {
|
||||
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
||||
const { prisma } = req;
|
||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||
console.log("req.query.apiKey", req.query.apiKey);
|
||||
|
||||
// We remove the prefix from the user provided api_key. If no env set default to "cal_"
|
||||
const strippedApiKey = `${req.query.apiKey}`.replace(process.env.API_KEY_PREFIX || "cal_", "");
|
||||
console.log("strippedApiKey", strippedApiKey);
|
||||
|
||||
// Hash the key again before matching against the database records.
|
||||
const hashedKey = hashAPIKey(strippedApiKey);
|
||||
console.log("hashedKey", hashedKey);
|
||||
// Check if the hashed api key exists in database.
|
||||
const apiKey = await prisma.apiKey.findUnique({ where: { hashedKey } });
|
||||
// console.log("apiKey", apiKey);
|
||||
@@ -35,5 +33,8 @@ export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
||||
if (!apiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" });
|
||||
/* We save the user id in the request for later use */
|
||||
req.userId = apiKey.userId;
|
||||
/* We save the isAdmin boolean here for later use */
|
||||
req.isAdmin = await isAdminGuard(req.userId);
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
type EventNameObjectType = {
|
||||
attendeeName: string;
|
||||
eventType: string;
|
||||
eventName?: string | null;
|
||||
host: string;
|
||||
location?: string;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
export function getEventName(eventNameObj: EventNameObjectType, forAttendeeView = false) {
|
||||
if (!eventNameObj.eventName)
|
||||
return eventNameObj.t("event_between_users", {
|
||||
eventName: eventNameObj.eventType,
|
||||
host: eventNameObj.host,
|
||||
attendeeName: eventNameObj.attendeeName,
|
||||
});
|
||||
|
||||
let eventName = eventNameObj.eventName;
|
||||
let locationString = "";
|
||||
|
||||
if (eventNameObj.eventName.includes("{LOCATION}")) {
|
||||
switch (eventNameObj.location) {
|
||||
case "inPerson":
|
||||
locationString = "In Person";
|
||||
break;
|
||||
case "userPhone":
|
||||
case "phone":
|
||||
locationString = "Phone";
|
||||
break;
|
||||
case "integrations:daily":
|
||||
locationString = "Cal Video";
|
||||
break;
|
||||
case "integrations:zoom":
|
||||
locationString = "Zoom";
|
||||
break;
|
||||
case "integrations:huddle01":
|
||||
locationString = "Huddle01";
|
||||
break;
|
||||
case "integrations:tandem":
|
||||
locationString = "Tandem";
|
||||
break;
|
||||
case "integrations:office365_video":
|
||||
locationString = "MS Teams";
|
||||
break;
|
||||
case "integrations:jitsi":
|
||||
locationString = "Jitsi";
|
||||
break;
|
||||
}
|
||||
eventName = eventName.replace("{LOCATION}", locationString);
|
||||
}
|
||||
|
||||
return (
|
||||
eventName
|
||||
// Need this for compatibility with older event names
|
||||
.replace("{USER}", eventNameObj.attendeeName)
|
||||
.replace("{ATTENDEE}", eventNameObj.attendeeName)
|
||||
.replace("{HOST}", eventNameObj.host)
|
||||
.replace("{HOST/ATTENDEE}", forAttendeeView ? eventNameObj.host : eventNameObj.attendeeName)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Webhook } from "@prisma/client";
|
||||
import { compile } from "handlebars";
|
||||
|
||||
// import type { CalendarEvent } from "@calcom/types/Calendar"; Add this to make it strict, change data: any to CalendarEvent type
|
||||
|
||||
type ContentType = "application/json" | "application/x-www-form-urlencoded";
|
||||
|
||||
function applyTemplate(template: string, data: any, contentType: ContentType) {
|
||||
const compiled = compile(template)(data);
|
||||
if (contentType === "application/json") {
|
||||
return JSON.stringify(jsonParse(compiled));
|
||||
}
|
||||
return compiled;
|
||||
}
|
||||
|
||||
function jsonParse(jsonString: string) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
// don't do anything.
|
||||
console.error(`error jsonParsing in sendPayload`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const sendPayload = async (
|
||||
triggerEvent: string,
|
||||
createdAt: string,
|
||||
webhook: Pick<Webhook, "subscriberUrl" | "appId" | "payloadTemplate">,
|
||||
data: any & {
|
||||
metadata?: { [key: string]: string };
|
||||
rescheduleUid?: string;
|
||||
bookingId?: number;
|
||||
}
|
||||
) => {
|
||||
const { subscriberUrl, appId, payloadTemplate: template } = webhook;
|
||||
if (!subscriberUrl || !data) {
|
||||
throw new Error("Missing required elements to send webhook payload.");
|
||||
}
|
||||
|
||||
const contentType =
|
||||
!template || jsonParse(template) ? "application/json" : "application/x-www-form-urlencoded";
|
||||
|
||||
data.description = data.description || data.additionalNotes;
|
||||
|
||||
let body;
|
||||
|
||||
/* Zapier id is hardcoded in the DB, we send the raw data for this case */
|
||||
if (appId === "zapier") {
|
||||
body = JSON.stringify(data);
|
||||
} else if (template) {
|
||||
body = applyTemplate(template, data, contentType);
|
||||
} else {
|
||||
body = JSON.stringify({
|
||||
triggerEvent: triggerEvent,
|
||||
createdAt: createdAt,
|
||||
payload: data,
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch(subscriberUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
message: text,
|
||||
};
|
||||
};
|
||||
|
||||
export default sendPayload;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { WebhookTriggerEvents } from "@prisma/client";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export type GetSubscriberOptions = {
|
||||
userId: number;
|
||||
eventTypeId: number;
|
||||
triggerEvent: WebhookTriggerEvents;
|
||||
};
|
||||
|
||||
const getWebhooks = async (options: GetSubscriberOptions) => {
|
||||
const { userId, eventTypeId } = options;
|
||||
const allWebhooks = await prisma.webhook.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{
|
||||
eventTypeId,
|
||||
},
|
||||
],
|
||||
AND: {
|
||||
eventTriggers: {
|
||||
has: options.triggerEvent,
|
||||
},
|
||||
active: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
subscriberUrl: true,
|
||||
payloadTemplate: true,
|
||||
appId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return allWebhooks;
|
||||
};
|
||||
|
||||
export default getWebhooks;
|
||||
@@ -3,7 +3,6 @@ import { z } from "zod";
|
||||
import { _BookingModel as Booking } from "@calcom/prisma/zod";
|
||||
|
||||
const schemaBookingBaseBodyParams = Booking.pick({
|
||||
uid: true,
|
||||
userId: true,
|
||||
eventTypeId: true,
|
||||
title: true,
|
||||
@@ -13,7 +12,6 @@ const schemaBookingBaseBodyParams = Booking.pick({
|
||||
|
||||
const schemaBookingCreateParams = z
|
||||
.object({
|
||||
uid: z.string(),
|
||||
eventTypeId: z.number(),
|
||||
title: z.string(),
|
||||
startTime: z.date().or(z.string()),
|
||||
@@ -25,7 +23,6 @@ export const schemaBookingCreateBodyParams = schemaBookingBaseBodyParams.merge(s
|
||||
|
||||
const schemaBookingEditParams = z
|
||||
.object({
|
||||
uid: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
startTime: z.date().optional(),
|
||||
endTime: z.date().optional(),
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { withValidation } from "next-validations";
|
||||
import { z } from "zod";
|
||||
|
||||
import { baseApiParams } from "./baseApiParams";
|
||||
|
||||
// Extracted out as utility function so can be reused
|
||||
// at different endpoints that require this validation.
|
||||
export const schemaQueryUserId = baseApiParams
|
||||
.extend({
|
||||
userId: z
|
||||
.string()
|
||||
.regex(/^\d+$/)
|
||||
.transform((id) => parseInt(id)),
|
||||
})
|
||||
.strict();
|
||||
+12
-7
@@ -59,7 +59,6 @@ export const schemaUserBaseBodyParams = User.pick({
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
endTime: true,
|
||||
metadata: true,
|
||||
bufferTime: true,
|
||||
theme: true,
|
||||
defaultScheduleId: true,
|
||||
@@ -93,7 +92,6 @@ const schemaUserEditParams = z.object({
|
||||
.optional()
|
||||
.nullable(),
|
||||
locale: z.nativeEnum(locales).optional().nullable(),
|
||||
metadata: jsonSchema,
|
||||
});
|
||||
|
||||
// @note: These are the values that are editable via PATCH method on the user Model,
|
||||
@@ -116,14 +114,20 @@ const schemaUserCreateParams = z.object({
|
||||
.optional()
|
||||
.nullable(),
|
||||
locale: z.nativeEnum(locales).optional(),
|
||||
metadata: jsonSchema,
|
||||
createdDate: z.string().or(z.date()).optional(),
|
||||
});
|
||||
|
||||
// @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(schemaUserEditParams).omit({});
|
||||
export const schemaUserCreateBodyParams = schemaUserBaseBodyParams.merge(schemaUserCreateParams).omit({});
|
||||
export const schemaUserEditBodyParams = schemaUserBaseBodyParams
|
||||
.merge(schemaUserEditParams)
|
||||
.omit({})
|
||||
.strict();
|
||||
|
||||
export const schemaUserCreateBodyParams = schemaUserBaseBodyParams
|
||||
.merge(schemaUserCreateParams)
|
||||
.omit({})
|
||||
.strict();
|
||||
|
||||
// @note: These are the values that are always returned when reading a user
|
||||
export const schemaUserReadPublic = User.pick({
|
||||
@@ -134,7 +138,6 @@ export const schemaUserReadPublic = User.pick({
|
||||
emailVerified: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
metadata: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
endTime: true,
|
||||
@@ -150,4 +153,6 @@ export const schemaUserReadPublic = User.pick({
|
||||
createdDate: true,
|
||||
verified: true,
|
||||
invitedTo: true,
|
||||
}).merge(schemaUserEditBodyParams);
|
||||
});
|
||||
|
||||
export const schemaUsersReadPublic = z.array(schemaUserReadPublic);
|
||||
|
||||
+8
-8
@@ -2,21 +2,21 @@
|
||||
// This makes our @calcom/prisma package from the monorepo to be transpiled and usable by API
|
||||
const withTM = require("next-transpile-modules")([
|
||||
"@calcom/app-store",
|
||||
"@calcom/console",
|
||||
"@calcom/prisma",
|
||||
"@calcom/types",
|
||||
"@calcom/core",
|
||||
"@calcom/stripe",
|
||||
"@calcom/emails",
|
||||
"@calcom/lib",
|
||||
"@calcom/ui",
|
||||
"@calcom/ee",
|
||||
"@calcom/lib",
|
||||
"@calcom/prisma",
|
||||
"@calcom/stripe",
|
||||
"@calcom/ui",
|
||||
"@calcom/emails",
|
||||
"@calcom/embed-core",
|
||||
"@calcom/embed-snippet",
|
||||
]);
|
||||
|
||||
module.exports = withTM({
|
||||
async rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
afterFiles: [
|
||||
// This redirects requests recieved at / the root to the /api/ folder.
|
||||
{
|
||||
source: "/v:version/:rest*",
|
||||
|
||||
+4
-4
@@ -7,10 +7,10 @@
|
||||
"author": "Cal.com Inc.",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
||||
"dev-real": "PORT=3002 next dev",
|
||||
"dev": "next build && PORT=3002 next start",
|
||||
"app-store:generate": "yarn workspace @calcom/app-store-cli generate",
|
||||
"build": "yarn app-store:generate && next build",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"dev": "PORT=3002 next dev",
|
||||
"lint-fix": "next lint --fix && prettier --write .",
|
||||
"lint": "next lint",
|
||||
"prebuild": "cd ../.. && yarn workspace @calcom/prisma generate-schemas",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// // import prisma from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { AttendeeResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaAttendeeEditBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee";
|
||||
import {
|
||||
schemaQueryIdParseInt,
|
||||
@@ -11,10 +9,9 @@ import {
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
export async function attendeeById(
|
||||
{ method, query, body, userId, prisma }: NextApiRequest,
|
||||
{ method, query, body, userId, isAdmin, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<AttendeeResponse>
|
||||
) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
if (!safeQuery.success) {
|
||||
res.status(400).json({ error: safeQuery.error });
|
||||
@@ -34,7 +31,7 @@ export async function attendeeById(
|
||||
.flat()
|
||||
.map((attendee) => attendee.id)
|
||||
);
|
||||
// @note: Here we make sure to only return attendee's of the user's own bookings.
|
||||
// @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin.
|
||||
if (!isAdmin) {
|
||||
if (!userBookingsAttendeeIds.includes(safeQuery.data.id))
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// import db from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { AttendeeResponse, AttendeesResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "@lib/validations/attendee";
|
||||
|
||||
async function createOrlistAllAttendees(
|
||||
{ method, userId, body, prisma }: NextApiRequest,
|
||||
{ method, userId, body, prisma, isAdmin }: NextApiRequest,
|
||||
res: NextApiResponse<AttendeesResponse | AttendeeResponse>
|
||||
) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
let attendees;
|
||||
if (!isAdmin) {
|
||||
const userBookings = await prisma.booking.findMany({
|
||||
@@ -124,7 +121,6 @@ async function createOrlistAllAttendees(
|
||||
} else (error: Error) => res.status(400).json({ error });
|
||||
}
|
||||
} else {
|
||||
// @todo: check real availability times before booking
|
||||
const data = await prisma.attendee.create({
|
||||
data: {
|
||||
email: safePost.data.email,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// // import prisma from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
// import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { AvailabilityResponse } from "@lib/types";
|
||||
import {
|
||||
schemaAvailabilityEditBodyParams,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// import prisma from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { AvailabilityResponse, AvailabilitiesResponse } from "@lib/types";
|
||||
import {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getUserAvailability } from "@calcom/core/getUserAvailability";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
||||
|
||||
const availabilitySchema = z
|
||||
.object({
|
||||
userId: stringOrNumber.optional(),
|
||||
username: z.string().optional(),
|
||||
dateFrom: z.string(),
|
||||
dateTo: z.string(),
|
||||
eventTypeId: stringOrNumber.optional(),
|
||||
})
|
||||
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { username, userId, eventTypeId, dateTo, dateFrom } = availabilitySchema.parse(req.query);
|
||||
return getUserAvailability({
|
||||
username,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
eventTypeId,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
export default defaultResponder(handler);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
|
||||
export default withMiddleware("HTTP_GET")(
|
||||
defaultHandler({
|
||||
GET: import("./_get"),
|
||||
})
|
||||
);
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// import prisma from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { BookingResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "@lib/validations/booking";
|
||||
import {
|
||||
schemaQueryIdParseInt,
|
||||
@@ -11,7 +9,7 @@ import {
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
export async function bookingById(
|
||||
{ method, query, body, userId, prisma }: NextApiRequest,
|
||||
{ method, query, body, userId, prisma, isAdmin }: NextApiRequest,
|
||||
res: NextApiResponse<BookingResponse>
|
||||
) {
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
@@ -25,7 +23,6 @@ export async function bookingById(
|
||||
});
|
||||
if (!userWithBookings) throw new Error("User not found");
|
||||
const userBookingIds = userWithBookings.bookings.map((booking: { id: number }) => booking.id).flat();
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
|
||||
if (!isAdmin) {
|
||||
if (!userBookingIds.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" });
|
||||
|
||||
+75
-16
@@ -1,16 +1,18 @@
|
||||
import { WebhookTriggerEvents } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// import prisma from "@calcom/prisma";
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { BookingResponse, BookingsResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { BookingResponse, BookingsResponse } from "@lib/types";
|
||||
import sendPayload from "@lib/utils/sendPayload";
|
||||
import getWebhooks from "@lib/utils/webhookSubscriptions";
|
||||
import { schemaBookingCreateBodyParams, schemaBookingReadPublic } from "@lib/validations/booking";
|
||||
import { schemaEventTypeReadPublic } from "@lib/validations/event-type";
|
||||
|
||||
async function createOrlistAllBookings(
|
||||
{ method, body, userId, prisma }: NextApiRequest,
|
||||
{ method, body, userId, isAdmin, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<BookingsResponse | BookingResponse>
|
||||
) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
if (method === "GET") {
|
||||
/**
|
||||
* @swagger
|
||||
@@ -93,18 +95,75 @@ async function createOrlistAllBookings(
|
||||
}
|
||||
if (!isAdmin) {
|
||||
safe.data.userId = userId;
|
||||
const data = await prisma.booking.create({ data: { ...safe.data } });
|
||||
const booking = schemaBookingReadPublic.parse(data);
|
||||
|
||||
if (booking) res.status(201).json({ booking, message: "Booking created successfully" });
|
||||
else (error: Error) => res.status(400).json({ error });
|
||||
} else {
|
||||
const data = await prisma.booking.create({ data: { ...safe.data } });
|
||||
const booking = schemaBookingReadPublic.parse(data);
|
||||
|
||||
if (booking) res.status(201).json({ booking, message: "Booking created successfully" });
|
||||
else (error: Error) => res.status(400).json({ error });
|
||||
}
|
||||
const data = await prisma.booking.create({ data: { uid: uuidv4(), ...safe.data } });
|
||||
const booking = schemaBookingReadPublic.parse(data);
|
||||
|
||||
if (booking) {
|
||||
const eventType = await prisma.eventType
|
||||
.findUnique({ where: { id: booking.eventTypeId as number } })
|
||||
.then((data) => schemaEventTypeReadPublic.parse(data))
|
||||
.catch((e: Error) => {
|
||||
console.error(`Event type with ID: ${booking.eventTypeId} not found`, e);
|
||||
});
|
||||
console.log(`eventType: ${eventType}`);
|
||||
const evt = {
|
||||
type: eventType?.title || booking.title,
|
||||
title: booking.title,
|
||||
description: "",
|
||||
additionalNotes: "",
|
||||
customInputs: {},
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
name: "",
|
||||
email: "",
|
||||
timeZone: "",
|
||||
language: {
|
||||
locale: "en",
|
||||
},
|
||||
},
|
||||
attendees: [],
|
||||
location: "",
|
||||
destinationCalendar: null,
|
||||
hideCalendar: false,
|
||||
uid: booking.uid,
|
||||
metadata: {},
|
||||
};
|
||||
console.log(`evt: ${evt}`);
|
||||
|
||||
// Send Webhook call if hooked to BOOKING_CREATED
|
||||
const triggerEvent = WebhookTriggerEvents.BOOKING_CREATED;
|
||||
console.log(`Trigger Event: ${triggerEvent}`);
|
||||
const subscriberOptions = {
|
||||
userId,
|
||||
eventTypeId: booking.eventTypeId as number,
|
||||
triggerEvent,
|
||||
};
|
||||
console.log(`subscriberOptions: ${subscriberOptions}`);
|
||||
|
||||
const subscribers = await getWebhooks(subscriberOptions);
|
||||
console.log(`subscribers: ${subscribers}`);
|
||||
const bookingId = booking?.id;
|
||||
const promises = subscribers.map((sub) =>
|
||||
sendPayload(triggerEvent, new Date().toISOString(), sub, {
|
||||
...evt,
|
||||
bookingId,
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing webhook for event: ${triggerEvent}, URL: ${sub.subscriberUrl}`, e);
|
||||
})
|
||||
);
|
||||
await Promise.all(promises);
|
||||
console.log("All promises resolved! About to send the response");
|
||||
res.status(201).json({ booking, message: "Booking created successfully" });
|
||||
} else
|
||||
(error: Error) => {
|
||||
console.log(error);
|
||||
res.status(400).json({
|
||||
message: "Could not create new booking",
|
||||
error,
|
||||
});
|
||||
};
|
||||
} else res.status(405).json({ message: `Method ${method} not allowed` });
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { EventTypeResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaEventTypeEditBodyParams, schemaEventTypeReadPublic } from "@lib/validations/event-type";
|
||||
import {
|
||||
schemaQueryIdParseInt,
|
||||
@@ -10,10 +9,9 @@ import {
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
export async function eventTypeById(
|
||||
{ method, query, body, userId, prisma }: NextApiRequest,
|
||||
{ method, query, body, userId, isAdmin, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<EventTypeResponse>
|
||||
) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
if (!safeQuery.success) {
|
||||
res.status(400).json({ message: "Your query was invalid" });
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { EventTypeResponse, EventTypesResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { EventTypeResponse, EventTypesResponse } from "@lib/types";
|
||||
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "@lib/validations/event-type";
|
||||
|
||||
async function createOrlistAllEventTypes(
|
||||
{ method, body, userId, prisma }: NextApiRequest,
|
||||
{ method, body, userId, isAdmin, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<EventTypesResponse | EventTypeResponse>
|
||||
) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
if (method === "GET") {
|
||||
/**
|
||||
* @swagger
|
||||
|
||||
@@ -93,6 +93,26 @@ export async function WebhookById(
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (safeBody.data.eventTypeId) {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
eventTypes: {
|
||||
some: {
|
||||
id: safeBody.data.eventTypeId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
members: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Team should be available and the user should be a member of the team
|
||||
if (!team?.members.some((membership) => membership.userId === userId)) {
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
await prisma.webhook
|
||||
.update({ where: { id: safeQuery.data.id }, data: safeBody.data })
|
||||
.then((data) => schemaWebhookReadPublic.parse(data))
|
||||
|
||||
@@ -59,6 +59,26 @@ async function createOrlistAllWebhooks(
|
||||
res.status(400).json({ message: "Invalid request body" });
|
||||
return;
|
||||
}
|
||||
if (safe.data.eventTypeId) {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
eventTypes: {
|
||||
some: {
|
||||
id: safe.data.eventTypeId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
members: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Team should be available and the user should be a member of the team
|
||||
if (!team?.members.some((membership) => membership.userId === userId)) {
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
const data = await prisma.webhook.create({ data: { id: uuidv4(), ...safe.data, userId } });
|
||||
if (data) res.status(201).json({ webhook: data, message: "Webhook created successfully" });
|
||||
else
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { UserResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import {
|
||||
schemaQueryIdParseInt,
|
||||
withValidQueryIdTransformParseInt,
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
import { schemaUserEditBodyParams, schemaUserReadPublic } from "@lib/validations/user";
|
||||
|
||||
export async function userById(
|
||||
{ method, query, body, userId, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<UserResponse>
|
||||
) {
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
console.log(body);
|
||||
if (!safeQuery.success) {
|
||||
res.status(400).json({ message: "Your query was invalid" });
|
||||
return;
|
||||
}
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
// Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user
|
||||
if (!isAdmin) {
|
||||
if (safeQuery.data.id !== userId) res.status(401).json({ message: "Unauthorized" });
|
||||
} else {
|
||||
switch (method) {
|
||||
case "GET":
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* get:
|
||||
* summary: Find a user, returns your user if regular user.
|
||||
* operationId: getUserById
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 4
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to get
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: User was not found
|
||||
*/
|
||||
|
||||
await prisma.user
|
||||
.findUnique({ where: { id: safeQuery.data.id } })
|
||||
.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 })
|
||||
);
|
||||
break;
|
||||
case "PATCH":
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* patch:
|
||||
* summary: Edit an existing user
|
||||
* operationId: editUserById
|
||||
* requestBody:
|
||||
* description: Edit an existing attendee related to one of your bookings
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* weekStart:
|
||||
* type: string
|
||||
* enum: [Monday, Sunday, Saturday]
|
||||
* example: Monday
|
||||
* brandColor:
|
||||
* type: string
|
||||
* example: "#FF000F"
|
||||
* darkBrandColor:
|
||||
* type: string
|
||||
* example: "#000000"
|
||||
* timeZone:
|
||||
* type: string
|
||||
* example: Europe/London
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 4
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to edit
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, user edited successfuly
|
||||
* 400:
|
||||
* description: Bad request. User body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
|
||||
const safeBody = schemaUserEditBodyParams.safeParse(body);
|
||||
if (!safeBody.success) {
|
||||
res.status(400).json({ message: "Bad request", error: safeBody.error });
|
||||
|
||||
return;
|
||||
}
|
||||
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 (
|
||||
safeBody?.data?.defaultScheduleId &&
|
||||
!userSchedulesIds.includes(Number(safeBody?.data?.defaultScheduleId))
|
||||
) {
|
||||
res.status(400).json({
|
||||
message: "Bad request: Invalid default schedule id",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await prisma.user
|
||||
.update({
|
||||
where: { id: userId },
|
||||
data: safeBody.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 })
|
||||
);
|
||||
break;
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* delete:
|
||||
* summary: Remove an existing user
|
||||
* operationId: removeUserById
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 1
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to delete
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, user removed successfuly
|
||||
* 400:
|
||||
* description: Bad request. User id is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
|
||||
case "DELETE":
|
||||
await prisma.user
|
||||
.delete({ where: { id: safeQuery.data.id } })
|
||||
.then(() =>
|
||||
res.status(200).json({ message: `User with id: ${safeQuery.data.id} deleted successfully` })
|
||||
)
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({ message: `User 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(userById));
|
||||
@@ -1,144 +0,0 @@
|
||||
// import availability from "@calcom/core/availability";
|
||||
// export default availability;
|
||||
import { Prisma } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// import prisma from "@calcom/prisma";
|
||||
import { asNumberOrThrow, asStringOrNull } from "@calcom/lib/asStringOrNull";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
import getBusyTimes from "@calcom/lib/getBusyTimes";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { prisma } = req;
|
||||
const id = asNumberOrThrow(req.query.id);
|
||||
const dateFrom = dayjs(asStringOrNull(req.query.dateFrom));
|
||||
const dateTo = dayjs(asStringOrNull(req.query.dateTo));
|
||||
const eventTypeId = typeof req.query.eventTypeId === "string" ? parseInt(req.query.eventTypeId) : undefined;
|
||||
|
||||
if (!dateFrom.isValid() || !dateTo.isValid()) {
|
||||
return res.status(400).json({ message: "Invalid time range given." });
|
||||
}
|
||||
|
||||
const rawUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
credentials: true,
|
||||
timeZone: true,
|
||||
bufferTime: true,
|
||||
availability: true,
|
||||
id: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
selectedCalendars: true,
|
||||
schedules: {
|
||||
select: {
|
||||
availability: true,
|
||||
timeZone: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
defaultScheduleId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const getEventType = (id: number) =>
|
||||
prisma.eventType.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
seatsPerTimeSlot: true,
|
||||
timeZone: true,
|
||||
schedule: {
|
||||
select: {
|
||||
availability: true,
|
||||
timeZone: true,
|
||||
},
|
||||
},
|
||||
availability: {
|
||||
select: {
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
days: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type EventType = Prisma.PromiseReturnType<typeof getEventType>;
|
||||
let eventType: EventType | null = null;
|
||||
if (eventTypeId) eventType = await getEventType(eventTypeId);
|
||||
|
||||
if (!rawUser) throw new Error("No user found");
|
||||
|
||||
const { selectedCalendars, ...currentUser } = rawUser;
|
||||
|
||||
const busyTimes = await getBusyTimes({
|
||||
credentials: currentUser.credentials,
|
||||
startTime: dateFrom.format(),
|
||||
endTime: dateTo.format(),
|
||||
eventTypeId,
|
||||
userId: currentUser.id,
|
||||
selectedCalendars,
|
||||
});
|
||||
|
||||
const bufferedBusyTimes = busyTimes.map((a) => ({
|
||||
start: dayjs(a.start).subtract(currentUser.bufferTime, "minute"),
|
||||
end: dayjs(a.end).add(currentUser.bufferTime, "minute"),
|
||||
}));
|
||||
|
||||
const schedule = eventType?.schedule
|
||||
? { ...eventType?.schedule }
|
||||
: {
|
||||
...currentUser.schedules.filter(
|
||||
(schedule) => !currentUser.defaultScheduleId || schedule.id === currentUser.defaultScheduleId
|
||||
)[0],
|
||||
};
|
||||
|
||||
const timeZone = schedule.timeZone || eventType?.timeZone || currentUser.timeZone;
|
||||
|
||||
const workingHours = getWorkingHours(
|
||||
{
|
||||
timeZone,
|
||||
},
|
||||
schedule.availability ||
|
||||
(eventType?.availability.length ? eventType.availability : currentUser.availability)
|
||||
);
|
||||
|
||||
/* Current logic is if a booking is in a time slot mark it as busy, but seats can have more than one attendee so grab
|
||||
current bookings with a seats event type and display them on the calendar, even if they are full */
|
||||
let currentSeats;
|
||||
if (eventType?.seatsPerTimeSlot) {
|
||||
currentSeats = await prisma.booking.findMany({
|
||||
where: {
|
||||
eventTypeId: eventTypeId,
|
||||
startTime: {
|
||||
gte: dateFrom.format(),
|
||||
lte: dateTo.format(),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
uid: true,
|
||||
startTime: true,
|
||||
_count: {
|
||||
select: {
|
||||
attendees: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
busy: bufferedBusyTimes,
|
||||
timeZone,
|
||||
workingHours,
|
||||
currentSeats,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaQueryUserId } from "@lib/validations/shared/queryUserId";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* delete:
|
||||
* summary: Remove an existing user
|
||||
* operationId: removeUserById
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 1
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to delete
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, user removed successfuly
|
||||
* 400:
|
||||
* description: Bad request. User id is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function deleteHandler(req: NextApiRequest) {
|
||||
const query = schemaQueryUserId.parse(req.query);
|
||||
const isAdmin = await isAdminGuard(req.userId);
|
||||
// Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user
|
||||
if (!isAdmin && query.userId !== req.userId)
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: query.userId } });
|
||||
if (!user) throw new HttpError({ statusCode: 404, message: "User not found" });
|
||||
|
||||
await prisma.user.delete({ where: { id: user.id } });
|
||||
return { message: `User with id: ${user.id} deleted successfully` };
|
||||
}
|
||||
|
||||
export default defaultResponder(deleteHandler);
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaQueryUserId } from "@lib/validations/shared/queryUserId";
|
||||
import { schemaUserReadPublic } from "@lib/validations/user";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* get:
|
||||
* summary: Find a user, returns your user if regular user.
|
||||
* operationId: getUserById
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 4
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to get
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: User was not found
|
||||
*/
|
||||
export async function getHandler(req: NextApiRequest) {
|
||||
const query = schemaQueryUserId.parse(req.query);
|
||||
const isAdmin = await isAdminGuard(req.userId);
|
||||
// Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user
|
||||
if (!isAdmin && query.userId !== req.userId)
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
const data = await prisma.user.findUnique({ where: { id: query.userId } });
|
||||
const user = schemaUserReadPublic.parse(data);
|
||||
return { user };
|
||||
}
|
||||
|
||||
export default defaultResponder(getHandler);
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaQueryUserId } from "@lib/validations/shared/queryUserId";
|
||||
import { schemaUserEditBodyParams, schemaUserReadPublic } from "@lib/validations/user";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{id}:
|
||||
* patch:
|
||||
* summary: Edit an existing user
|
||||
* operationId: editUserById
|
||||
* requestBody:
|
||||
* description: Edit an existing attendee related to one of your bookings
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* weekStart:
|
||||
* type: string
|
||||
* enum: [Monday, Sunday, Saturday]
|
||||
* example: Monday
|
||||
* brandColor:
|
||||
* type: string
|
||||
* example: "#FF000F"
|
||||
* darkBrandColor:
|
||||
* type: string
|
||||
* example: "#000000"
|
||||
* timeZone:
|
||||
* type: string
|
||||
* example: Europe/London
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* example: 4
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the user to edit
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, user edited successfuly
|
||||
* 400:
|
||||
* description: Bad request. User body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function patchHandler(req: NextApiRequest) {
|
||||
const query = schemaQueryUserId.parse(req.query);
|
||||
const isAdmin = await isAdminGuard(req.userId);
|
||||
// Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user
|
||||
if (!isAdmin && query.userId !== req.userId)
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
|
||||
const body = schemaUserEditBodyParams.parse(req.body);
|
||||
const userSchedules = await prisma.schedule.findMany({
|
||||
where: { userId: req.userId },
|
||||
});
|
||||
const userSchedulesIds = userSchedules.map((schedule) => schedule.id);
|
||||
// @note: here we make sure user can only make as default his own scheudles
|
||||
if (body.defaultScheduleId && !userSchedulesIds.includes(Number(body.defaultScheduleId))) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "Bad request: Invalid default schedule id",
|
||||
});
|
||||
}
|
||||
const data = await prisma.user.update({
|
||||
where: { id: req.userId },
|
||||
data: body,
|
||||
});
|
||||
const user = schemaUserReadPublic.parse(data);
|
||||
return { user };
|
||||
}
|
||||
|
||||
export default defaultResponder(patchHandler);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
|
||||
export default withMiddleware("HTTP_GET")(
|
||||
defaultHandler({
|
||||
GET: import("@api/availability/_get"),
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defaultHandler } from "@/../../packages/lib/server";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { withValidQueryIdTransformParseInt } from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
|
||||
withValidQueryIdTransformParseInt(
|
||||
defaultHandler({
|
||||
GET: import("./_get"),
|
||||
PATCH: import("./_patch"),
|
||||
DELETE: import("./_delete"),
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaUsersReadPublic } from "@lib/validations/user";
|
||||
|
||||
import { Prisma } from ".prisma/client";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users:
|
||||
* get:
|
||||
* operationId: listUsers
|
||||
* summary: Find all users.
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: No users were found
|
||||
*/
|
||||
async function getHandler({ userId }: NextApiRequest) {
|
||||
const isAdmin = await isAdminGuard(userId);
|
||||
const where: Prisma.UserWhereInput = {};
|
||||
// If user is not ADMIN, return only his data.
|
||||
if (!isAdmin) where.id = userId;
|
||||
const data = await prisma.user.findMany({ where });
|
||||
const users = schemaUsersReadPublic.parse(data);
|
||||
return { users };
|
||||
}
|
||||
|
||||
export default defaultResponder(getHandler);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { HttpError } from "@/../../packages/lib/http-error";
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaUserCreateBodyParams } from "@lib/validations/user";
|
||||
|
||||
async function postHandler(req: NextApiRequest) {
|
||||
const isAdmin = await isAdminGuard(req.userId);
|
||||
// If user is not ADMIN, return unauthorized.
|
||||
if (!isAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" });
|
||||
const data = schemaUserCreateBodyParams.parse(req.body);
|
||||
const user = await prisma.user.create({ data });
|
||||
req.statusCode = 201;
|
||||
return { user };
|
||||
}
|
||||
|
||||
export default defaultResponder(postHandler);
|
||||
@@ -1,10 +1,12 @@
|
||||
<<<<<<< HEAD
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
=======
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
>>>>>>> main
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { UserResponse, UsersResponse } from "@lib/types";
|
||||
import { isAdminGuard } from "@lib/utils/isAdmin";
|
||||
import { schemaUserReadPublic, schemaUserCreateBodyParams } from "@lib/validations/user";
|
||||
|
||||
<<<<<<< HEAD
|
||||
/**
|
||||
* @swagger
|
||||
* /users:
|
||||
@@ -56,3 +58,11 @@ async function getAllorCreateUser(
|
||||
}
|
||||
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(getAllorCreateUser);
|
||||
=======
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(
|
||||
defaultHandler({
|
||||
GET: import("./_get"),
|
||||
POST: import("./_post"),
|
||||
})
|
||||
);
|
||||
>>>>>>> main
|
||||
|
||||
Reference in New Issue
Block a user