Refactors teams

pull/9078/head
zomars 2022-10-10 20:25:47 -06:00
parent c03144c343
commit 88332fb2ab
8 changed files with 82 additions and 100 deletions

View File

@ -2,10 +2,14 @@ import { z } from "zod";
import { _TeamModel as Team } from "@calcom/prisma/zod";
export const schemaTeamBaseBodyParams = Team.omit({ id: true }).partial();
export const schemaTeamBaseBodyParams = Team.omit({ id: true }).partial({ hideBranding: true });
const schemaTeamRequiredParams = z.object({});
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams);
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict();
export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial();
export const schemaTeamReadPublic = Team.omit({});
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);

View File

@ -0,0 +1,16 @@
import type { NextApiRequest } from "next";
import { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
async function authMiddleware(req: NextApiRequest) {
const { userId, prisma, isAdmin } = req;
const { teamId } = schemaQueryTeamId.parse(req.query);
/** Admins can skip the ownership verification */
if (isAdmin) return;
/** Non-members will see a 404 error which may or not be the desired behavior. */
await prisma.team.findFirstOrThrow({
where: { id: teamId, members: { some: { userId } } },
});
}
export default authMiddleware;

View File

@ -1,4 +1,4 @@
import type { NextApiRequest, NextApiResponse } from "next";
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
@ -22,36 +22,22 @@ import { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
* - teams
* responses:
* 201:
* description: OK, team removed successfuly
* description: OK, team removed successfully
* 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 },
export async function deleteHandler(req: NextApiRequest) {
const { prisma, query, userId } = req;
const { teamId } = schemaQueryTeamId.parse(query);
/** Only OWNERS can delete teams */
const _team = await prisma.team.findFirst({
where: { id: teamId, members: { some: { userId, role: "OWNER" } } },
});
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,
})
);
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER required" });
await prisma.team.delete({ where: { id: teamId } });
return { message: `Team with id: ${teamId} deleted successfully` };
}
export default defaultResponder(deleteHandler);

View File

@ -1,6 +1,6 @@
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 { schemaQueryTeamId } from "@lib/validations/shared/queryTeamId";
@ -31,18 +31,12 @@ import { schemaTeamReadPublic } from "@lib/validations/team";
*/
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 };
const { teamId } = schemaQueryTeamId.parse(req.query);
const where: Prisma.TeamWhereInput = { id: teamId };
// Non-admins can only query the teams they're part of
if (!isAdmin) where.members = { some: { userId } };
const data = await prisma.team.findFirstOrThrow({ where });
return { team: schemaTeamReadPublic.parse(data) };
}
export default defaultResponder(getHandler);

View File

@ -1,10 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
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 { schemaTeamBodyParams, schemaTeamReadPublic } from "@lib/validations/team";
import { schemaTeamReadPublic, schemaTeamUpdateBodyParams } from "@lib/validations/team";
/**
* @swagger
@ -23,35 +23,23 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "@lib/validations/tea
* - teams
* responses:
* 201:
* description: OK, team edited successfuly
* description: OK, team edited successfully
* 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 },
export async function patchHandler(req: NextApiRequest) {
const { prisma, body, userId } = req;
const data = schemaTeamUpdateBodyParams.parse(body);
const { teamId } = schemaQueryTeamId.parse(req.query);
/** Only OWNERS and ADMINS can edit teams */
const _team = await prisma.team.findFirst({
where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
});
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;
}
}
const data = await prisma.team.update({ where: { id: query.teamId }, data: safeBody.data });
if (!data) throw new HttpError({ statusCode: 404, message: `Team with id: ${query.teamId} not found` });
const team = schemaTeamReadPublic.parse(data);
if (!team) throw new HttpError({ statusCode: 401, message: `Your request body wasn't valid` });
return { team };
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
const team = await prisma.team.update({ where: { id: teamId }, data });
return { team: schemaTeamReadPublic.parse(team) };
}
export default defaultResponder(patchHandler);

View File

@ -1,14 +1,18 @@
import { defaultHandler } from "@calcom/lib/server";
import { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { withMiddleware } from "@lib/helpers/withMiddleware";
import { withValidQueryTeamId } from "@lib/validations/shared/queryTeamId";
export default withMiddleware()(
withValidQueryTeamId(
defaultHandler({
import authMiddleware from "./_auth-middleware";
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
await authMiddleware(req);
return defaultHandler({
GET: import("./_get"),
PATCH: import("./_patch"),
DELETE: import("./_delete"),
})
)
})(req, res);
})
);

View File

@ -3,7 +3,7 @@ import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaTeamReadPublic } from "@lib/validations/team";
import { schemaTeamsReadPublic } from "@lib/validations/team";
/**
* @swagger
@ -23,20 +23,11 @@ import { schemaTeamReadPublic } from "@lib/validations/team";
*/
async function getHandler(req: NextApiRequest) {
const { userId, prisma, isAdmin } = req;
const membershipWhere: Prisma.MembershipWhereInput = {};
const where: Prisma.TeamWhereInput = {};
// 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 };
if (!isAdmin) where.members = { some: { userId } };
const data = await prisma.team.findMany({ where });
return { teams: schemaTeamsReadPublic.parse(data) };
}
export default defaultResponder(getHandler);

View File

@ -1,6 +1,5 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { schemaMembershipPublic } from "@lib/validations/membership";
@ -24,22 +23,22 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "@lib/validations/tea
*/
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" });
const data = schemaTeamBodyParams.parse(body);
const team = await prisma.team.create({
data: {
...data,
members: {
// We're also creating the relation membership of team ownership in this call.
create: { userId, role: "OWNER", accepted: true },
},
},
include: { members: true },
});
req.statusCode = 201;
// We are also returning the new ownership relation as owner besides team.
return {
team,
owner,
team: schemaTeamReadPublic.parse(team),
owner: schemaMembershipPublic.parse(team.members[0]),
message: "Team created successfully, we also made you the owner of this team",
};
}