mirror of
https://github.com/detleph/server.git
synced 2026-09-04 08:36:06 +02:00
Merge branch 'feature-roles' into feature-endpoints
This commit is contained in:
@@ -6,7 +6,7 @@ import argon2 from "argon2";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { DataType, generateInvalidBodyError } from "./common";
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || "secret";
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
const TOKEN_EXPIRY = "4 days";
|
||||
|
||||
export interface AuthJWTPayload {
|
||||
@@ -18,6 +18,10 @@ export interface AuthJWTPayload {
|
||||
}
|
||||
|
||||
function createAdminJWT(admin: Admin & { groups: Group[] }) {
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error("JWT_SECRET not set");
|
||||
}
|
||||
|
||||
const payload: AuthJWTPayload = {
|
||||
pid: admin.pid,
|
||||
name: admin.name,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AdminLevel, Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||
import { Request, Response } from "express";
|
||||
import { ZodError } from "zod";
|
||||
import prisma from "../lib/prisma";
|
||||
|
||||
interface StringIndexedObject {
|
||||
@@ -23,6 +24,7 @@ export enum DataType {
|
||||
NUMBER = "number",
|
||||
INTEGER = "integer",
|
||||
PERMISSION_LEVEL = "'ELEVATED' | 'STANDARD'",
|
||||
JOB = "'TEAMLEADER' | 'MEMBER'",
|
||||
DATETIME = "ISOstring",
|
||||
UUID = "string",
|
||||
RESULT_SCHEMA = "result_schema",
|
||||
@@ -32,7 +34,18 @@ interface Body {
|
||||
[k: string]: DataType;
|
||||
}
|
||||
|
||||
export function generateInvalidBodyError(body: Body) {
|
||||
export function generateInvalidBodyError(body: Body, zodError?: ZodError) {
|
||||
if (zodError instanceof ZodError) {
|
||||
return {
|
||||
type: "error",
|
||||
payload: {
|
||||
message: "The body of your request did not conform to the requirements",
|
||||
errors: { body: zodError.format() },
|
||||
schema: { body },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "error",
|
||||
payload: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Prisma, Organisation, Admin, AdminLevel, Team } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||
import { Request, Response } from "express";
|
||||
import { z } from "zod";
|
||||
import prisma from "../lib/prisma";
|
||||
import ForwardableError from "../Middleware/error/ForwardableError";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
@@ -16,16 +17,20 @@ import {
|
||||
|
||||
require("express-async-errors");
|
||||
|
||||
const DisciplineBody = z.object({
|
||||
name: z.string(),
|
||||
maxTeamSize: z.number(),
|
||||
minTeamSize: z.number(),
|
||||
briefDescription: z.string(),
|
||||
fullDescription: z.string(),
|
||||
eventPid: z.string(),
|
||||
})
|
||||
|
||||
const UpdateDisciplineBody = DisciplineBody.partial();
|
||||
const InitialDisciplineBody = z.object({
|
||||
name: z.string().min(1),
|
||||
minTeamSize: z.number(),
|
||||
maxTeamSize: z.number(),
|
||||
});
|
||||
|
||||
const disciplineRefiner = [
|
||||
(args: any) => (args.minTeamSize && args.maxTeamSize ? args.minTeamSize <= args.maxTeamSize : true),
|
||||
{ message: "The minTeamSize must be smaller or equal to the maxTeamSize" },
|
||||
] as const;
|
||||
|
||||
const DisciplineBody = InitialDisciplineBody.refine(...disciplineRefiner);
|
||||
const updateDisciplineBody = InitialDisciplineBody.partial().refine(...disciplineRefiner);
|
||||
|
||||
const basicDiscipline = {
|
||||
pid: true,
|
||||
@@ -220,37 +225,27 @@ export const createDiscipline = async (req: Request<{ eventPid: string }, {}, Cr
|
||||
return res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
|
||||
const { name, minTeamSize, maxTeamSize, briefDescription, fullDescription } = req.body;
|
||||
const result = DisciplineBody.safeParse(req.body);
|
||||
|
||||
if (typeof name !== "string" ||
|
||||
typeof minTeamSize !== "number" ||
|
||||
typeof maxTeamSize !== "number" ||
|
||||
typeof briefDescription !== "string" ||
|
||||
(fullDescription && typeof fullDescription !== "string")
|
||||
) {
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError({
|
||||
name: DataType.STRING,
|
||||
minTeamSize: DataType.NUMBER,
|
||||
maxTeamSize: DataType.NUMBER,
|
||||
})
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
name: DataType.STRING,
|
||||
minTeamSize: DataType.NUMBER,
|
||||
maxTeamSize: DataType.NUMBER,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!validateName(name)) {
|
||||
return res.status(400).json(NAME_ERROR);
|
||||
}
|
||||
const { name, minTeamSize, maxTeamSize } = result.data;
|
||||
|
||||
try {
|
||||
const discipline = await prisma.discipline.create({
|
||||
data: { name, minTeamSize, maxTeamSize, briefDescription, fullDescription, event: { connect: { pid: req.params.eventPid } } },
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
minTeamSize: true,
|
||||
maxTeamSize: true,
|
||||
event: { select: { pid: true, name: true } },
|
||||
},
|
||||
data: { name, minTeamSize, maxTeamSize, event: { connect: { pid: req.params.eventPid } } },
|
||||
select: basicDiscipline,
|
||||
});
|
||||
|
||||
return res.status(201).json({ type: "success", payload: { discipline } });
|
||||
@@ -262,12 +257,56 @@ export const createDiscipline = async (req: Request<{ eventPid: string }, {}, Cr
|
||||
}
|
||||
};
|
||||
|
||||
interface DeleteDisciplineQueryParams {
|
||||
pid: string;
|
||||
}
|
||||
// requires: auth(ELEVATED)
|
||||
export const updateDiscipline = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
if (req.auth?.permission_level !== "ELEVATED") {
|
||||
res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
|
||||
const result = updateDisciplineBody.safeParse(req.body); // FIXME: Useres can currently use two requests to forgo min/max team size checking altogether
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
name: DataType.STRING,
|
||||
minTeamSize: DataType.NUMBER,
|
||||
maxTeamSize: DataType.NUMBER,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = result.data;
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
const discipline = await prisma.discipline.update({
|
||||
where: { pid },
|
||||
data: {
|
||||
name: body.name,
|
||||
minTeamSize: body.minTeamSize,
|
||||
maxTeamSize: body.maxTeamSize,
|
||||
},
|
||||
select: basicDiscipline,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
type: "success",
|
||||
payload: { discipline },
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("discipline", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
// requires: auth(ELEVATED)
|
||||
export const deleteDiscipline = async (req: Request<DeleteDisciplineQueryParams>, res: Response) => {
|
||||
export const deleteDiscipline = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
if (req.auth?.permission_level !== "ELEVATED") {
|
||||
res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
|
||||
@@ -182,12 +182,15 @@ export const updateEvent = async (req: Request<{ pid: string }>, res: Response)
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError({
|
||||
name: DataType.STRING,
|
||||
date: DataType.DATETIME,
|
||||
briefDescription: DataType.STRING,
|
||||
["fullDescription?"]: DataType.STRING,
|
||||
})
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
name: DataType.STRING,
|
||||
date: DataType.DATETIME,
|
||||
briefDescription: DataType.STRING,
|
||||
["fullDescription?"]: DataType.STRING,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -264,7 +267,7 @@ export const deleteEvent = async (req: Request<DeleteEventQueryParams>, res: Res
|
||||
return res.status(204).end();
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
return res.status(404).json(generateError(`The organisation with the ID ${pid} could not be found`));
|
||||
return res.status(404).json(generateError(`The event with the ID ${pid} could not be found`));
|
||||
}
|
||||
|
||||
throw e;
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import { Admin } from "@prisma/client";
|
||||
import { Admin, Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||
import { Request, Response } from "express";
|
||||
import { z } from "zod";
|
||||
import prisma from "../lib/prisma";
|
||||
import { createInsufficientPermissionsError, generateError, genericError, handleCreateByName } from "./common";
|
||||
import { requireResponsibleForGroup } from "../Middleware/auth/auth";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
import {
|
||||
createInsufficientPermissionsError,
|
||||
DataType,
|
||||
generateError,
|
||||
generateInvalidBodyError,
|
||||
genericError,
|
||||
handleCreateByName,
|
||||
} from "./common";
|
||||
|
||||
const updateGroupBody = z
|
||||
.object({
|
||||
name: z.string().min(1),
|
||||
user_limit: z.number().int().positive(),
|
||||
level: z.number().int().nonnegative(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
const basicGroup = {
|
||||
pid: true,
|
||||
name: true,
|
||||
level: true,
|
||||
organisation: { select: { pid: true, name: true } },
|
||||
} as const;
|
||||
|
||||
@@ -115,6 +134,52 @@ export const createGroup = async (req: Request<{ organisationPid: string }, {},
|
||||
);
|
||||
};
|
||||
|
||||
// requires: auth(STANDARD with GROUP permission)
|
||||
export const updateGroup = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
const result = updateGroupBody.safeParse(req.body);
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
name: DataType.STRING,
|
||||
user_limit: DataType.NUMBER,
|
||||
level: DataType.NUMBER,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = result.data;
|
||||
const { pid } = req.params;
|
||||
|
||||
requireResponsibleForGroup(req.auth, pid);
|
||||
|
||||
try {
|
||||
const group = await prisma.group.update({
|
||||
where: { pid },
|
||||
data: {
|
||||
name: body.name,
|
||||
user_limit: body.user_limit,
|
||||
level: body.level,
|
||||
},
|
||||
select: basicGroup,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
type: "success",
|
||||
payload: { group },
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("group", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
interface DeleteGroupQueryParams {
|
||||
pid: string;
|
||||
}
|
||||
@@ -132,7 +197,7 @@ export const deleteGroup = async (req: Request<DeleteGroupQueryParams>, res: Res
|
||||
return res.status(204).end();
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
return res.status(404).json(generateError(`The group with the ID ${pid} could not be found`));
|
||||
throw new NotFoundError("group", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import prisma from "../lib/prisma";
|
||||
import { z } from "zod";
|
||||
import { Request, Response } from "express";
|
||||
import {
|
||||
AUTH_ERROR,
|
||||
createInsufficientPermissionsError,
|
||||
DataType,
|
||||
generateError,
|
||||
generateInvalidBodyError,
|
||||
} from "./common";
|
||||
import { Job, Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
import { requireResponsibleForGroup } from "../Middleware/auth/auth";
|
||||
|
||||
//TODO: add TeamleaderAuthentification
|
||||
|
||||
// REVIEW: All this code should be able to be executed by the teamleader of the team the participant is in AND
|
||||
// an admin the group of whom overlaps with the team AND an elevated admin
|
||||
|
||||
const ParticipantBody = z.object({
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
groupId: z.string().uuid(),
|
||||
//job: z.enum(["TEAMLEADER", "MEMBER"]),
|
||||
});
|
||||
|
||||
const returnedParticipant = {
|
||||
pid: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
relevance: true,
|
||||
team: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
// REVIEW: Location of this endpoints (/groups, /teams, /participants, ...?)
|
||||
export const createParticipant = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
const result = ParticipantBody.safeParse(req.body);
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
firstname: DataType.STRING,
|
||||
lastName: DataType.STRING,
|
||||
groupId: DataType.UUID,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
const body = result.data;
|
||||
const { pid } = req.params;
|
||||
|
||||
/*
|
||||
if (!req.auth?.isAuthenticated || req.teamleader?.team != pid) {
|
||||
return res.status(500).json(AUTH_ERROR);
|
||||
}
|
||||
if (req.teamleader?.team != pid) {
|
||||
return res.status(500).json(AUTH_ERROR);
|
||||
}
|
||||
requireResponsibleForGroup(req.auth, req.body.groupId);
|
||||
*/
|
||||
|
||||
try {
|
||||
const participant = await prisma.participant.create({
|
||||
data: {
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
relevance: "MEMBER",
|
||||
group: { connect: { pid: body.groupId } },
|
||||
team: { connect: { pid } },
|
||||
},
|
||||
select: returnedParticipant,
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
type: "success",
|
||||
payload: { participant },
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
return res
|
||||
.status(404)
|
||||
.json(generateError(`Could not link to team with ID '${pid}, or group with ID ${body.groupId}'`));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateParticipant = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
//insert TeamleaderAuth
|
||||
|
||||
const result = ParticipantBody.partial().safeParse(req.body); // Should be partial, right?
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
firstname: DataType.STRING,
|
||||
lastName: DataType.STRING,
|
||||
groupId: DataType.UUID,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = result.data;
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
const participant = await prisma.participant.update({
|
||||
where: { pid },
|
||||
data: {
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
group: { connect: { pid: body.groupId } },
|
||||
},
|
||||
select: returnedParticipant,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
type: "success",
|
||||
payload: { participant },
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("participant", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteParticipant = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
//insert TeamleaderAuth
|
||||
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
await prisma.participant.delete({ where: { pid } });
|
||||
|
||||
return res.status(204).end();
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
return res.status(404).json(generateError(`The participant with the ID ${pid} could not be found`));
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Prisma, Role } from "@prisma/client";
|
||||
import { Request, Response } from "express";
|
||||
import { z } from "zod";
|
||||
import prisma from "../lib/prisma";
|
||||
import { requireLeaderOfTeam, requireResponsibleForParticipant } from "../Middleware/auth/teamleaderAuth";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
import { createInsufficientPermissionsError, DataType, generateInvalidBodyError } from "./common";
|
||||
|
||||
require("express-async-errors");
|
||||
|
||||
/**
|
||||
*
|
||||
* @param teamPid: Pid of the team to add the roles to
|
||||
* @returns Count of roles added (As roles are helper objects, there should be no need for more)
|
||||
*/
|
||||
export async function createRolesForTeam(teamPid: string) {
|
||||
const schemas = await prisma.roleSchema.findMany({ where: { discipline: { teams: { some: { pid: teamPid } } } } });
|
||||
|
||||
const teamId = (await prisma.team.findUnique({ where: { pid: teamPid } }))?.id;
|
||||
|
||||
if (!teamId) {
|
||||
throw new NotFoundError("team", teamPid);
|
||||
}
|
||||
|
||||
const roles = await prisma.role.createMany({
|
||||
data: schemas.map((schema) => ({ schemaId: schema.id, score: "", teamId })), // TODO: Use default score from schema?
|
||||
});
|
||||
|
||||
return roles.count;
|
||||
}
|
||||
|
||||
export async function getRolesForTeam(req: Request<{ teamPid: string }>, res: Response) {
|
||||
const teamPid = req.params.teamPid;
|
||||
|
||||
requireLeaderOfTeam(req.teamleader, teamPid);
|
||||
|
||||
const roles = await prisma.role.findMany({
|
||||
where: { team: { pid: teamPid } },
|
||||
select: {
|
||||
pid: true,
|
||||
score: true,
|
||||
schema: { select: { pid: true } },
|
||||
participant: { select: { pid: true, firstName: true, lastName: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
type: "success",
|
||||
payload: {
|
||||
roles,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const AssignParticipantToRoleBody = z.object({
|
||||
participantPid: z.string().uuid(),
|
||||
});
|
||||
|
||||
// requires: auth(leader of the team)
|
||||
export async function assignParticipantToRole(req: Request<{ pid: string }>, res: Response) {
|
||||
const zBody = AssignParticipantToRoleBody.safeParse(req.body);
|
||||
|
||||
if (zBody.success === false) {
|
||||
return res.status(400).json(generateInvalidBodyError({ participantPid: DataType.UUID }, zBody.error));
|
||||
}
|
||||
|
||||
const { participantPid } = zBody.data;
|
||||
const rolePid = req.params.pid;
|
||||
|
||||
requireResponsibleForParticipant(req.teamleader, participantPid);
|
||||
|
||||
const schema = await prisma.role.findFirst({
|
||||
where: { pid: rolePid, team: { participants: { some: { pid: participantPid } } } },
|
||||
select: { participant: { select: { pid: true, firstName: true, lastName: true } } },
|
||||
});
|
||||
|
||||
if (!schema) {
|
||||
return res.status(404).json({
|
||||
type: "error",
|
||||
payload: {
|
||||
message: `No role with the ID '${rolePid}' could be found in the scope of the participant with the ID '${participantPid}'`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// No error handling should be neccesary as the existence of the role and participant have already been checked above
|
||||
await prisma.role.update({ where: { pid: rolePid }, data: { participant: { connect: { pid: participantPid } } } });
|
||||
|
||||
return res.status(200).json({
|
||||
type: "success",
|
||||
payload: {
|
||||
message: `The participant with ID '${participantPid}' was successfully assigned to the role with ID '${rolePid}'`,
|
||||
...(schema.participant ? { unassigned: schema.participant } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const updateRoleScore = async (req: Request<{ pid: string }, {}, { score: string }>, res: Response) => {
|
||||
if (req.auth?.permission_level != "ELEVATED") {
|
||||
res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
|
||||
const { score } = req.body;
|
||||
|
||||
if (typeof score !== "string") {
|
||||
res.status(400).json(generateInvalidBodyError({ score: DataType.STRING }));
|
||||
}
|
||||
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
const role = await prisma.role.update({
|
||||
where: { pid },
|
||||
data: { score },
|
||||
select: {
|
||||
pid: true,
|
||||
score: true,
|
||||
schema: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
participant: {
|
||||
select: {
|
||||
pid: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
type: "success",
|
||||
payload: {
|
||||
role,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("role", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export async function deleteRolesFromTeam(teamPid: string) {
|
||||
await prisma.role.deleteMany({
|
||||
where: { team: { pid: teamPid } },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||
import { Request, Response } from "express";
|
||||
import { z } from "zod";
|
||||
import prisma from "../lib/prisma";
|
||||
import { DurationSchemaT, parseSchema, PointSchemaT } from "../lib/result_schema";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
@@ -8,12 +9,21 @@ import SchemaError from "../Middleware/error/SchemaError";
|
||||
import {
|
||||
createInsufficientPermissionsError,
|
||||
DataType,
|
||||
generateError,
|
||||
generateInvalidBodyError,
|
||||
NAME_ERROR,
|
||||
validateName,
|
||||
} from "./common";
|
||||
|
||||
const RoleSchemaBody = z.object({
|
||||
name: z.string().min(1),
|
||||
schema: z.string(),
|
||||
});
|
||||
|
||||
const UpdateRoleSchema = RoleSchemaBody.partial();
|
||||
|
||||
const roleSchema = {
|
||||
pid: true,
|
||||
name: true,
|
||||
schema: true,
|
||||
discipline: { select: { pid: true, name: true } },
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Request, Response } from "express";
|
||||
import prisma from "../lib/prisma";
|
||||
import { createInsufficientPermissionsError, DataType, generateInvalidBodyError } from "./common";
|
||||
import { requireLeaderOfTeam } from "../Middleware/auth/teamleaderAuth";
|
||||
import { z } from "zod";
|
||||
|
||||
const TeamBody = z.object({
|
||||
teamName: z.string().min(1),
|
||||
leaderEmail: z.string().email(),
|
||||
disciplineId: z.string().uuid(),
|
||||
partFirstName: z.string().min(1),
|
||||
partLastName: z.string().min(1),
|
||||
partGroupId: z.string().uuid(),
|
||||
});
|
||||
|
||||
interface CreateTeamBody {
|
||||
teamName: string;
|
||||
leaderEmail: string;
|
||||
disciplineId: string;
|
||||
partFirstName: string;
|
||||
partLastName: string;
|
||||
partGroupId: string;
|
||||
}
|
||||
|
||||
export const getTeams = async (req: Request, res: Response) => {
|
||||
const teams = prisma.team.findMany({ select: { pid: true, name: true, disciplineId: true } });
|
||||
|
||||
res.status(200).json(teams);
|
||||
};
|
||||
|
||||
export const getTeam = async (req: Request, res: Response) => {
|
||||
const { pid } = req.params;
|
||||
|
||||
const team = prisma.team.findUnique({
|
||||
where: { pid },
|
||||
select: {
|
||||
disciplineId: true,
|
||||
name: true,
|
||||
pid: true,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json(team);
|
||||
};
|
||||
|
||||
export const updateTeam = async (req: Request, res: Response) => {
|
||||
const result = TeamBody.merge(z.object({ pid: z.string().min(1) }))
|
||||
.omit({ partGroupId: true, partFirstName: true, partLastName: true })
|
||||
.safeParse(req.body);
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
teamName: DataType.STRING,
|
||||
leaderEmail: DataType.STRING,
|
||||
disciplineId: DataType.UUID,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = result.data;
|
||||
|
||||
try {
|
||||
requireLeaderOfTeam(req.teamleader, body.pid);
|
||||
} catch {
|
||||
return res.status(401).json(createInsufficientPermissionsError("STANDARD"));
|
||||
}
|
||||
|
||||
const team = prisma.team.update({
|
||||
where: {
|
||||
pid: body.pid,
|
||||
},
|
||||
data: {
|
||||
name: body.teamName,
|
||||
discipline: { connect: { pid: body.disciplineId } },
|
||||
leaderEmail: body.leaderEmail,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(204).json(team);
|
||||
};
|
||||
|
||||
export const deleteTeam = async (req: Request, res: Response) => {
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
requireLeaderOfTeam(req.teamleader, pid);
|
||||
} catch {
|
||||
return res.status(401).json(createInsufficientPermissionsError("STANDARD"));
|
||||
}
|
||||
|
||||
prisma.team.delete({ where: { pid } });
|
||||
|
||||
res.status(204).json("Welp its gone");
|
||||
};
|
||||
@@ -3,27 +3,113 @@ import prisma from "../lib/prisma";
|
||||
import { mailClient } from "../lib/redis";
|
||||
import { nanoid } from "nanoid";
|
||||
import { verificationMail } from "../lib/mail";
|
||||
import { createInsufficientPermissionsError, DataType, generateInvalidBodyError } from "./common";
|
||||
import { generateTeamleaderJWT, requireLeaderOfTeam } from "../Middleware/auth/teamleaderAuth";
|
||||
import { createRolesForTeam } from "./role.controller";
|
||||
import { any, z } from "zod";
|
||||
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
//TODO: Implemnt user endpoint and use following code to send verification mail
|
||||
const TeamBody = z.object({
|
||||
teamName: z.string().min(1),
|
||||
leaderEmail: z.string().email(),
|
||||
disciplineId: z.string().uuid(),
|
||||
partFirstName: z.string().min(1),
|
||||
partLastName: z.string().min(1),
|
||||
partGroupId: z.string().uuid(),
|
||||
});
|
||||
|
||||
const user = {
|
||||
//Supposed to come from database
|
||||
id: "10",
|
||||
email: "[email protected]",
|
||||
};
|
||||
interface CreateTeamBody {
|
||||
teamName: string;
|
||||
leaderEmail: string;
|
||||
disciplineId: string;
|
||||
partFirstName: string;
|
||||
partLastName: string;
|
||||
partGroupId: string;
|
||||
}
|
||||
|
||||
// TODO: Some kind of auth (Teamleader probably)
|
||||
export const register = async (req: Request<{}, {}, CreateTeamBody>, res: Response) => {
|
||||
const result = TeamBody.safeParse(req.body);
|
||||
|
||||
if (result.success === false) {
|
||||
return res.status(400).json(
|
||||
generateInvalidBodyError(
|
||||
{
|
||||
teamName: DataType.STRING,
|
||||
leaderEmail: DataType.STRING,
|
||||
disciplineId: DataType.UUID,
|
||||
partFirstName: DataType.STRING,
|
||||
partLastName: DataType.STRING,
|
||||
partGroupId: DataType.UUID,
|
||||
},
|
||||
result.error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = result.data;
|
||||
|
||||
const team = await prisma.team.create({
|
||||
data: {
|
||||
leaderEmail: body.leaderEmail,
|
||||
name: body.teamName,
|
||||
roles: undefined,
|
||||
discipline: { connect: { pid: body.disciplineId } },
|
||||
participants: {
|
||||
create: {
|
||||
firstName: body.partFirstName,
|
||||
lastName: body.partLastName,
|
||||
relevance: "TEAMLEADER",
|
||||
group: { connect: { pid: body.partGroupId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
disciplineId: true,
|
||||
},
|
||||
});
|
||||
|
||||
//TODO: maybe use returned amount of created use?
|
||||
createRolesForTeam(team.pid);
|
||||
|
||||
const usid = nanoid();
|
||||
|
||||
(await mailClient).set(usid, user.id);
|
||||
(await mailClient).set(usid, team.pid);
|
||||
|
||||
verificationMail(user.email, "eventname", usid);
|
||||
verificationMail(req.body.leaderEmail, "eventname", usid);
|
||||
|
||||
//Send status code
|
||||
res.status(201).json({ type: "success", payload: { team } });
|
||||
};
|
||||
|
||||
export const requestToken = async (req: Request, res: Response) => {
|
||||
const { teamId } = req.body || {};
|
||||
|
||||
if (!(typeof teamId === "string")) {
|
||||
return res.status(400).json(generateInvalidBodyError({ teamId: DataType.STRING }));
|
||||
}
|
||||
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
pid: teamId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
return res.status(404).json();
|
||||
}
|
||||
|
||||
const usid = nanoid();
|
||||
|
||||
(await mailClient).set(usid, team.pid);
|
||||
|
||||
verificationMail(team.leaderEmail, "eventname", usid);
|
||||
|
||||
res.status(200).json({ type: "sucess", message: "Email sent!" });
|
||||
};
|
||||
|
||||
export const verifyEmail = async (req: Request, res: Response) => {
|
||||
const { code } = req.body || {};
|
||||
const { code } = req.params || {};
|
||||
|
||||
if (!(typeof code === "string")) {
|
||||
return res.status(400).json({
|
||||
@@ -45,12 +131,23 @@ export const verifyEmail = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
prisma.participant.update({
|
||||
const team = await prisma.team.update({
|
||||
where: {
|
||||
id: parseInt(acc),
|
||||
pid: acc,
|
||||
},
|
||||
data: {
|
||||
verified: true,
|
||||
},
|
||||
});
|
||||
|
||||
mailClient.set(code, "");
|
||||
|
||||
const token = generateTeamleaderJWT(team);
|
||||
|
||||
res.cookie("teamLeaderToken", token, {
|
||||
path: "/",
|
||||
maxAge: 1000 * 60 * 60 * 24 * 4,
|
||||
});
|
||||
|
||||
res.status(200).json({ type: "succes", payload: { token } }); //TODO: This needs to set a cookie or smth so that the client also gets this info
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user