feat: refactor teams and add team availability
parent
6ed007d873
commit
be2647790c
|
@ -0,0 +1,21 @@
|
|||
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 schemaQueryTeamId = baseApiParams
|
||||
.extend({
|
||||
teamId: z
|
||||
.string()
|
||||
.regex(/^\d+$/)
|
||||
.transform((id) => parseInt(id)),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const withValidQueryTeamId = withValidation({
|
||||
schema: schemaQueryTeamId,
|
||||
type: "Zod",
|
||||
mode: "query",
|
||||
});
|
|
@ -8,4 +8,4 @@ const schemaTeamRequiredParams = z.object({});
|
|||
|
||||
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams);
|
||||
|
||||
export const schemaTeamPublic = Team.omit({});
|
||||
export const schemaTeamReadPublic = Team.omit({});
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import type { TeamResponse } from "@lib/types";
|
||||
import {
|
||||
schemaQueryIdParseInt,
|
||||
withValidQueryIdTransformParseInt,
|
||||
} from "@lib/validations/shared/queryIdTransformParseInt";
|
||||
import { schemaTeamBodyParams, schemaTeamPublic } from "@lib/validations/team";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /teams/{id}:
|
||||
* get:
|
||||
* operationId: getTeamById
|
||||
* summary: Find a team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to get
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: Team was not found
|
||||
* patch:
|
||||
* operationId: editTeamById
|
||||
* summary: Edit an existing team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to edit
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team edited successfuly
|
||||
* 400:
|
||||
* description: Bad request. Team body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* delete:
|
||||
* operationId: removeTeamById
|
||||
* summary: Remove an existing team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to delete
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team removed successfuly
|
||||
* 400:
|
||||
* description: Bad request. Team id is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function teamById(
|
||||
{ method, query, body, userId, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<TeamResponse>
|
||||
) {
|
||||
const safeQuery = schemaQueryIdParseInt.safeParse(query);
|
||||
const safeBody = schemaTeamBodyParams.safeParse(body);
|
||||
if (!safeQuery.success) {
|
||||
res.status(400).json({ message: "Your query was invalid" });
|
||||
return;
|
||||
}
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
//FIXME: This is a hack to get the teamId from the user's membership
|
||||
console.log(userWithMemberships);
|
||||
const userTeamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
if (!userTeamIds.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" });
|
||||
else {
|
||||
switch (method) {
|
||||
case "GET":
|
||||
await prisma.team
|
||||
.findUnique({ where: { id: safeQuery.data.id } })
|
||||
.then((data) => schemaTeamPublic.parse(data))
|
||||
.then((team) => res.status(200).json({ team }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({
|
||||
message: `Team 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.team
|
||||
.update({ where: { id: safeQuery.data.id }, data: safeBody.data })
|
||||
.then((team) => schemaTeamPublic.parse(team))
|
||||
.then((team) => res.status(200).json({ team }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({
|
||||
message: `Team with id: ${safeQuery.data.id} not found`,
|
||||
error,
|
||||
})
|
||||
);
|
||||
break;
|
||||
|
||||
case "DELETE":
|
||||
await prisma.team
|
||||
.delete({ where: { id: safeQuery.data.id } })
|
||||
.then(() =>
|
||||
res.status(200).json({
|
||||
message: `Team with id: ${safeQuery.data.id} deleted successfully`,
|
||||
})
|
||||
)
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({
|
||||
message: `Team 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(teamById));
|
|
@ -0,0 +1,57 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/{teamId}:
|
||||
* delete:
|
||||
* operationId: removeTeamById
|
||||
* summary: Remove an existing team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: teamId
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to delete
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team removed successfuly
|
||||
* 400:
|
||||
* description: Bad request. Team id is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { prisma, isAdmin, userId } = req;
|
||||
|
||||
const query = schemaQueryTeamId.parse(req.query);
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
const userTeamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
// 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 && !userTeamIds.includes(query.teamId))
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
await prisma.team
|
||||
.delete({ where: { id: query.teamId } })
|
||||
.then(() =>
|
||||
res.status(200).json({
|
||||
message: `Team with id: ${query.teamId} deleted successfully`,
|
||||
})
|
||||
)
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({
|
||||
message: `Team with id: ${query.teamId} not found`,
|
||||
error,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default defaultResponder(deleteHandler);
|
|
@ -0,0 +1,48 @@
|
|||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
|
||||
import { schemaTeamReadPublic } from "@lib/validations/team";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /teams/{teamId}:
|
||||
* get:
|
||||
* operationId: getTeamById
|
||||
* summary: Find a team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: teamId
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to get
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: Team was not found
|
||||
*/
|
||||
export async function getHandler(req: NextApiRequest) {
|
||||
const { prisma, isAdmin, userId } = req;
|
||||
|
||||
const query = schemaQueryTeamId.parse(req.query);
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
const userTeamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
// 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 && !userTeamIds.includes(query.teamId))
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
const data = await prisma.team.findUnique({ where: { id: query.teamId } });
|
||||
const team = schemaTeamReadPublic.parse(data);
|
||||
return { team };
|
||||
}
|
||||
|
||||
export default defaultResponder(getHandler);
|
|
@ -0,0 +1,62 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
|
||||
import { schemaTeamBodyParams, schemaTeamReadPublic } from "@lib/validations/team";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /teams/{teamId}:
|
||||
* patch:
|
||||
* operationId: editTeamById
|
||||
* summary: Edit an existing team
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: teamId
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: ID of the team to edit
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team edited successfuly
|
||||
* 400:
|
||||
* description: Bad request. Team body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
export async function patchHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { prisma, isAdmin, userId, body } = req;
|
||||
const safeBody = schemaTeamBodyParams.safeParse(body);
|
||||
|
||||
const query = schemaQueryTeamId.parse(req.query);
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
const userTeamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
// 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 && !userTeamIds.includes(query.teamId))
|
||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||
if (!safeBody.success) {
|
||||
{
|
||||
res.status(400).json({ message: "Invalid request body" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
await prisma.team
|
||||
.update({ where: { id: query.teamId }, data: safeBody.data })
|
||||
.then((team) => schemaTeamReadPublic.parse(team))
|
||||
.then((team) => res.status(200).json({ team }))
|
||||
.catch((error: Error) =>
|
||||
res.status(404).json({
|
||||
message: `Team with id: ${query.teamId} not found`,
|
||||
error,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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 "@calcom/lib/server";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { withValidQueryTeamId } from "@lib/validations/shared/queryTeamId";
|
||||
|
||||
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
|
||||
withValidQueryTeamId(
|
||||
defaultHandler({
|
||||
GET: import("./_get"),
|
||||
PATCH: import("./_patch"),
|
||||
DELETE: import("./_delete"),
|
||||
})
|
||||
)
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaTeamReadPublic } from "@lib/validations/team";
|
||||
|
||||
import { Prisma } from ".prisma/client";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /teams:
|
||||
* get:
|
||||
* operationId: listTeams
|
||||
* summary: Find all teams
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: No teams were found
|
||||
*/
|
||||
async function getHandler(req: NextApiRequest) {
|
||||
const { userId, prisma, isAdmin } = req;
|
||||
const membershipWhere: Prisma.MembershipWhereInput = {};
|
||||
// If user is not ADMIN, return only his data.
|
||||
if (!isAdmin) membershipWhere.userId = userId;
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: membershipWhere,
|
||||
});
|
||||
const teamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
const teamWhere: Prisma.TeamWhereInput = {};
|
||||
|
||||
if (!isAdmin) teamWhere.id = { in: teamIds };
|
||||
|
||||
const data = await prisma.team.findMany({ where: teamWhere });
|
||||
const teams = schemaTeamReadPublic.parse(data);
|
||||
return { teams };
|
||||
}
|
||||
|
||||
export default defaultResponder(getHandler);
|
|
@ -0,0 +1,47 @@
|
|||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaMembershipPublic } from "@lib/validations/membership";
|
||||
import { schemaTeamBodyParams, schemaTeamReadPublic } from "@lib/validations/team";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /teams:
|
||||
* post:
|
||||
* operationId: addTeam
|
||||
* summary: Creates a new team
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team created
|
||||
* 400:
|
||||
* description: Bad request. Team body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
async function postHandler(req: NextApiRequest) {
|
||||
const { prisma, body, userId } = req;
|
||||
const safe = schemaTeamBodyParams.safeParse(body);
|
||||
if (!safe.success) throw new HttpError({ statusCode: 400, message: "Invalid request body" });
|
||||
const data = await prisma.team.create({ data: safe.data });
|
||||
// We're also creating the relation membership of team ownership in this call.
|
||||
const owner = await prisma.membership
|
||||
.create({
|
||||
data: { userId, teamId: data.id, role: "OWNER", accepted: true },
|
||||
})
|
||||
.then((owner) => schemaMembershipPublic.parse(owner));
|
||||
const team = schemaTeamReadPublic.parse(data);
|
||||
if (!team) throw new HttpError({ statusCode: 400, message: "We were not able to create your team" });
|
||||
req.statusCode = 201;
|
||||
// We are also returning the new ownership relation as owner besides team.
|
||||
return {
|
||||
team,
|
||||
owner,
|
||||
message: "Team created successfully, we also made you the owner of this team",
|
||||
};
|
||||
}
|
||||
|
||||
export default defaultResponder(postHandler);
|
|
@ -1,87 +1,10 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
import { withMiddleware } from "@lib/helpers/withMiddleware";
|
||||
import { TeamResponse, TeamsResponse } from "@lib/types";
|
||||
import { schemaMembershipPublic } from "@lib/validations/membership";
|
||||
import { schemaTeamBodyParams, schemaTeamPublic } from "@lib/validations/team";
|
||||
|
||||
async function createOrlistAllTeams(
|
||||
{ method, body, userId, prisma }: NextApiRequest,
|
||||
res: NextApiResponse<TeamsResponse | TeamResponse>
|
||||
) {
|
||||
if (method === "GET") {
|
||||
/**
|
||||
* @swagger
|
||||
* /teams:
|
||||
* get:
|
||||
* operationId: listTeams
|
||||
* summary: Find all teams
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: No teams were found
|
||||
*/
|
||||
const userWithMemberships = await prisma.membership.findMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
const teamIds = userWithMemberships.map((membership) => membership.teamId);
|
||||
const teams = await prisma.team.findMany({ where: { id: { in: teamIds } } });
|
||||
if (teams) res.status(200).json({ teams });
|
||||
else
|
||||
(error: Error) =>
|
||||
res.status(404).json({
|
||||
message: "No Teams were found",
|
||||
error,
|
||||
});
|
||||
} else if (method === "POST") {
|
||||
/**
|
||||
* @swagger
|
||||
* /teams:
|
||||
* post:
|
||||
* operationId: addTeam
|
||||
* summary: Creates a new team
|
||||
* tags:
|
||||
* - teams
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, team created
|
||||
* 400:
|
||||
* description: Bad request. Team body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
const safe = schemaTeamBodyParams.safeParse(body);
|
||||
if (!safe.success) {
|
||||
res.status(400).json({ message: "Invalid request body" });
|
||||
return;
|
||||
}
|
||||
const team = await prisma.team.create({ data: safe.data });
|
||||
// We're also creating the relation membership of team ownership in this call.
|
||||
const membership = await prisma.membership
|
||||
.create({
|
||||
data: { userId, teamId: team.id, role: "OWNER", accepted: true },
|
||||
})
|
||||
.then((membership) => schemaMembershipPublic.parse(membership));
|
||||
const data = schemaTeamPublic.parse(team);
|
||||
// We are also returning the new ownership relation as owner besides team.
|
||||
if (data)
|
||||
res.status(201).json({
|
||||
team: data,
|
||||
owner: membership,
|
||||
message: "Team created successfully, we also made you the owner of this team",
|
||||
});
|
||||
else
|
||||
(error: Error) =>
|
||||
res.status(400).json({
|
||||
message: "Could not create new team",
|
||||
error,
|
||||
});
|
||||
} else res.status(405).json({ message: `Method ${method} not allowed` });
|
||||
}
|
||||
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllTeams);
|
||||
export default withMiddleware("HTTP_GET_OR_POST")(
|
||||
defaultHandler({
|
||||
GET: import("./_get"),
|
||||
POST: import("./_post"),
|
||||
})
|
||||
);
|
||||
|
|
|
@ -5,6 +5,22 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||
|
||||
import { schemaUserCreateBodyParams } from "@lib/validations/user";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users:
|
||||
* post:
|
||||
* operationId: addUser
|
||||
* summary: Creates a new user
|
||||
* tags:
|
||||
* - users
|
||||
* responses:
|
||||
* 201:
|
||||
* description: OK, user created
|
||||
* 400:
|
||||
* description: Bad request. user body is invalid.
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
async function postHandler(req: NextApiRequest) {
|
||||
const { prisma, isAdmin } = req;
|
||||
// If user is not ADMIN, return unauthorized.
|
||||
|
|
Loading…
Reference in New Issue