diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1d07b9e..79ea5f2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,11 +34,13 @@ model Admin { } model Discipline { - id Int @id @default(autoincrement()) - pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid - name String - minTeamSize Int - maxTeamSize Int + id Int @id @default(autoincrement()) + pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + name String + briefDescription String + fullDescription String? + minTeamSize Int + maxTeamSize Int roles RoleSchema[] teams Team[] @@ -125,7 +127,7 @@ model Group { model Media { id Int @id @default(autoincrement()) pid String @unique - description String + description String @default("visual") events Event[] disciplines Discipline[] diff --git a/src/Controllers/discipline.controller.ts b/src/Controllers/discipline.controller.ts index 40d5494..c898603 100644 --- a/src/Controllers/discipline.controller.ts +++ b/src/Controllers/discipline.controller.ts @@ -1,10 +1,10 @@ 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"; +import { number, z } from "zod"; import { createInsufficientPermissionsError, DataType, @@ -20,6 +20,8 @@ const InitialDisciplineBody = z.object({ name: z.string().min(1), minTeamSize: z.number(), maxTeamSize: z.number(), + briefDescription: z.string().min(1), + fullDescription: z.string(), }); const disciplineRefiner = [ @@ -27,7 +29,7 @@ const disciplineRefiner = [ { message: "The minTeamSize must be smaller or equal to the maxTeamSize" }, ] as const; -const DisciplineBody = InitialDisciplineBody.refine(...disciplineRefiner); +const DisciplineBody = InitialDisciplineBody.partial({ fullDescription: true }).refine(...disciplineRefiner); const updateDisciplineBody = InitialDisciplineBody.partial().refine(...disciplineRefiner); const basicDiscipline = { @@ -36,6 +38,8 @@ const basicDiscipline = { visual: { select: { pid: true } }, maxTeamSize: true, minTeamSize: true, + briefDescription: true, + fullDescription: true, event: { select: { pid: true, name: true } }, roles: { select: { pid: true, name: true } }, } as const; @@ -132,6 +136,8 @@ interface CreateDisciplineBody { name?: string; minTeamSize?: number; maxTeamSize?: number; + briefDescription?: string; + fullDescription?: string; } // require: auth(ELEVATED) @@ -150,17 +156,19 @@ export const createDiscipline = async (req: Request<{ eventPid: string }, {}, Cr name: DataType.STRING, minTeamSize: DataType.NUMBER, maxTeamSize: DataType.NUMBER, + briefDescription: DataType.STRING, + ["fullDescription?"]: DataType.STRING, }, result.error ) ); } - const { name, minTeamSize, maxTeamSize } = result.data; + const { name, minTeamSize, maxTeamSize, briefDescription } = result.data; try { const discipline = await prisma.discipline.create({ - data: { name, minTeamSize, maxTeamSize, event: { connect: { pid: req.params.eventPid } } }, + data: { name, minTeamSize, maxTeamSize, briefDescription, event: { connect: { pid: req.params.eventPid } } }, select: basicDiscipline, }); @@ -188,6 +196,8 @@ export const updateDiscipline = async (req: Request<{ pid: string }>, res: Respo name: DataType.STRING, minTeamSize: DataType.NUMBER, maxTeamSize: DataType.NUMBER, + briefDescription: DataType.STRING, + ["fullDescription?"]: DataType.STRING, }, result.error ) @@ -204,6 +214,8 @@ export const updateDiscipline = async (req: Request<{ pid: string }>, res: Respo name: body.name, minTeamSize: body.minTeamSize, maxTeamSize: body.maxTeamSize, + briefDescription: body.briefDescription, + fullDescription: body.fullDescription, }, select: basicDiscipline, }); @@ -241,63 +253,3 @@ export const deleteDiscipline = async (req: Request<{ pid: string }>, res: Respo throw e; } }; - -interface visualParams { - disciplinePid: string; -} - -interface visualBody { - mediaPid: string; -} - -export const addVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { disciplinePid } = req.params; - - const discipline = await prisma.discipline.update({ - where: { pid: disciplinePid }, - data: { - visual: { connect: { pid: req.body.mediaPid } }, - }, - }); - - // TODO: This does not work and should be updated in all addVisual-type code segments - // Reason: update throw a PrismaClientKnownRequestError with code P2025 if the record to update could not be found - if (!discipline) { - throw new NotFoundError("discipline", disciplinePid); - } - - return res.status(200).json({ - type: "success", - payload: {}, - }); -}; - -export const deleteVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { disciplinePid, pid } = req.params; - - try { - await prisma.discipline.update({ - where: { - pid: disciplinePid, - }, - data: { - visual: { disconnect: { pid } }, - }, - }); - return res.status(204).end(); - } catch (e) { - if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { - throw new NotFoundError("discipline", disciplinePid); // Refer: Last todo; This is a correct example - } - - throw e; - } -}; diff --git a/src/Controllers/event.controller.ts b/src/Controllers/event.controller.ts index 03d0426..3dfcd84 100644 --- a/src/Controllers/event.controller.ts +++ b/src/Controllers/event.controller.ts @@ -1,6 +1,6 @@ import { Prisma } from "@prisma/client"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime"; -import { Request, Response } from "express"; +import e, { Request, Response } from "express"; import { z } from "zod"; import prisma from "../lib/prisma"; import NotFoundError from "../Middleware/error/NotFoundError"; @@ -8,17 +8,49 @@ import { createInsufficientPermissionsError, DataType, generateError, generateIn require("express-async-errors"); +export const dateSchema = z.preprocess((arg) => { + if (typeof arg == "string" || arg instanceof Date) return new Date(arg); +}, z.date()); + +const EventBody = z.object({ + name: z.string().min(1), + date: dateSchema, + briefDescription: z.string(), + fullDescription: z.string(), +}); + +const UpdateBody = EventBody.partial(); + +const CreateEventBody = EventBody.partial({ + fullDescription: true, +}); + +const basicEvent = { + pid: true, + name: true, + date: true, + briefDescription: true, + fullDescription: true, +} as const; + +const detailedEvent = { + pid: true, + name: true, + date: true, + briefDescription: true, + fullDescription: true, + visual: { select: { pid: true, description: true } }, + disciplines: { + select: { + pid: true, + name: true, + }, + }, +} as const; + export const getAllEvents = async (req: Request, res: Response) => { const events = await prisma.event.findMany({ - select: { - name: true, - briefDescription: true, - fullDescription: true, - visual: { select: { pid: true, description: true } }, - date: true, - pid: true, - id: false, - }, + select: detailedEvent, }); res.status(200).json({ @@ -45,15 +77,7 @@ export const getEvent = async (req: Request, res: Response) => { where: { pid: eventId, }, - select: { - name: true, - briefDescription: true, - fullDescription: true, - date: true, - pid: true, - id: false, - visual: { select: { pid: true, description: true } }, - }, + select: detailedEvent, }); if (!event) { @@ -96,24 +120,23 @@ export const addEvent = async (req: Request, res: Response) => { if (req.auth?.permission_level !== "ELEVATED") { res.status(403).json(createInsufficientPermissionsError()); } - if ( - typeof req.body.name !== "string" || - typeof req.body.date !== "string" || - typeof req.body.briefDescription !== "string" || - (req.body.fullDescription && typeof req.body.fullDescription !== "string") - ) { + + const result = CreateEventBody.safeParse(req.body); + + 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 + ) ); } - //TODO: Check if date is valid - const event = await prisma.event.create({ data: { name: req.body.name, @@ -121,14 +144,7 @@ export const addEvent = async (req: Request, res: Response) => { briefDescription: req.body.briefDescription, fullDescription: req.body.fullDescription, }, - select: { - name: true, - date: true, - pid: true, - id: false, - briefDescription: true, - fullDescription: true, - }, + select: basicEvent, }); res.status(201).json({ @@ -139,15 +155,6 @@ export const addEvent = async (req: Request, res: Response) => { }); }; -const EventBody = z.object({ - name: z.string(), - date: z.string(), - briefDescription: z.string(), - fullDescription: z.string(), -}); - -const UpdateBody = EventBody.partial(); - export const updateEvent = async (req: Request<{ pid: string }>, res: Response) => { if (req.auth?.permission_level !== "ELEVATED") { res.status(403).json(createInsufficientPermissionsError()); @@ -191,10 +198,6 @@ export const updateEvent = async (req: Request<{ pid: string }>, res: Response) }, }); - if (!event) { - throw new NotFoundError("event", pid); - } - res.status(200).json({ type: "success", payload: { @@ -202,24 +205,8 @@ export const updateEvent = async (req: Request<{ pid: string }>, res: Response) }, }); } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - return res.status(500).json({ - type: "error", - payload: { - message: `Internal Server error occured. Try again later`, - }, - }); - } - if (e instanceof Prisma.PrismaClientUnknownRequestError) { - return res.status(500).json({ - type: "error", - payload: { - message: "Unknown error occurred with your request. Check if your parameters are correct", - schema: { - eventId: DataType.UUID, - }, - }, - }); + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError("discipline", pid); } throw e; @@ -244,71 +231,7 @@ export const deleteEvent = async (req: Request, res: Res return res.status(204).end(); } catch (e) { if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { - return res.status(404).json(generateError(`The event with the ID ${pid} could not be found`)); - } - - throw e; - } -}; - -// REVIEW: This code **will** need to be de-duplicated - -interface visualParams { - eventPid: string; -} - -interface visualBody { - mediaPid: string; -} - -export const addVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { eventPid } = req.params; - - if (typeof req.body.mediaPid !== "string") { - res.status(400).json(generateInvalidBodyError({ mediaPid: DataType.STRING })); - } - - const event = await prisma.event.update({ - where: { pid: eventPid }, - data: { - visual: { connect: { pid: req.body.mediaPid } }, - }, - }); - - if (!event) { - throw new NotFoundError("event", eventPid); - } - - return res.status(200).json({ - type: "success", - payload: {}, - }); -}; - -export const deleteVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { eventPid, pid } = req.params; - - try { - await prisma.event.update({ - where: { - pid: eventPid, - }, - data: { - visual: { disconnect: { pid } }, - }, - }); - return res.status(204).end(); - } catch (e) { - if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { - throw new NotFoundError("discipline", eventPid); + throw new NotFoundError("discipline", pid); } throw e; diff --git a/src/Controllers/group.controllers.ts b/src/Controllers/group.controllers.ts index 686ed7a..355a997 100644 --- a/src/Controllers/group.controllers.ts +++ b/src/Controllers/group.controllers.ts @@ -192,7 +192,7 @@ export const deleteGroup = async (req: Request, res: Res const { pid } = req.params; try { - prisma.group.delete({ where: { pid } }); + await prisma.group.delete({ where: { pid } }); return res.status(204).end(); } catch (e) { diff --git a/src/Controllers/media.controller.ts b/src/Controllers/media.controller.ts index eb78bc3..11a2cc7 100644 --- a/src/Controllers/media.controller.ts +++ b/src/Controllers/media.controller.ts @@ -1,4 +1,4 @@ -import { Request, response, Response } from "express"; +import { Request, Response } from "express"; import fs from "fs"; import isSvg from "is-svg"; import { fromBuffer as fileTypeFromBuffer } from "file-type"; @@ -8,10 +8,8 @@ import prisma from "../lib/prisma"; import NotFoundError from "../Middleware/error/NotFoundError"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime"; import { generateInvalidBodyError, DataType } from "./common"; -import { type } from "os"; import { unlink } from "fs/promises"; import ForwardableError from "../Middleware/error/ForwardableError"; -import SchemaError from "../Middleware/error/SchemaError"; require("express-async-errors"); @@ -99,7 +97,6 @@ export const uploadImage = async (req: Request, res: Response) => { const fileName = file.md5 + (fileIsSvg ? ".svg" : "." + fileType?.ext); try { - //generate record const media = await prisma.media.create({ data: { pid: fileName, @@ -189,3 +186,71 @@ export const deleteMedia = async (req: Request, res: Response) => { throw e; } }; + +export const linkMedia = async (req: Request<{ pid: string }, {}, { mediaPid: string }>, res: Response) => { + if (req.auth?.permission_level != "ELEVATED") { + res.status(403).json(createInsufficientPermissionsError()); + } + + const { pid } = req.params; + const { mediaPid } = req.body; + const tableToUpdate = req.originalUrl.split("/"); + + if (typeof mediaPid !== "string") { + return res.status(400).json( + generateInvalidBodyError({ + mediaPid: DataType.UUID, + }) + ); + } + + const updatedRec = await getPrismaUpdateFKT(tableToUpdate[2])({ + where: { pid }, + data: { + visual: { connect: { pid: mediaPid } }, + }, + }); + + if (!updatedRec) { + throw new NotFoundError(tableToUpdate[2], pid); + } + + return res.status(200).json({ + type: "success", + payload: {}, + }); +}; + +export const unlinkMedia = async (req: Request<{ pid: string; mediaPid: string }>, res: Response) => { + if (req.auth?.permission_level != "ELEVATED") { + res.status(403).json(createInsufficientPermissionsError()); + } + + const { pid, mediaPid } = req.params; + const tableToUpdate = req.originalUrl.split("/"); + + try { + await getPrismaUpdateFKT(tableToUpdate[2])({ + where: { pid }, + data: { visual: { disconnect: { pid: mediaPid } } }, + }); + return res.status(204).end(); + } catch (e) { + if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError(tableToUpdate[2], pid); + } + + throw e; + } +}; + +function getPrismaUpdateFKT(tableToUpdate: string): Function { + switch (tableToUpdate) { + case "events": + return prisma.event.update; + case "disciplines": + return prisma.discipline.update; + default: + return prisma.roleSchema.update; + } +} diff --git a/src/Controllers/organisation.controller.ts b/src/Controllers/organisation.controller.ts index 146594e..5a701fe 100644 --- a/src/Controllers/organisation.controller.ts +++ b/src/Controllers/organisation.controller.ts @@ -12,6 +12,7 @@ import { } from "./common"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime"; import { Prisma } from "@prisma/client"; +import NotFoundError from "../Middleware/error/NotFoundError"; function validateOranisationName(name: string) { return name.length > 0; @@ -187,16 +188,12 @@ export const updateOrganisation = async ( }, }); } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - if (e.code === "P2025") { - return res.status(404).json(generateError(`The organisation with the ID ${pid} could not be found`)); - } - } else if (e instanceof PrismaClientUnknownRequestError) { - return res.status(400).send(generateError("Unkonwn error occured. This could be due to malformed IDs")); + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError("discipline", pid); } - } - return res.status(500).json(genericError); + throw e; + } }; interface DeleteOrganisationQueryParams { @@ -216,7 +213,7 @@ export const deleteOrganisation = async (req: Request, res: Response) => { +// at: POST api/teams/:teamPid/participant/ +export const createParticipant = async (req: Request, res: Response) => { const result = ParticipantBody.safeParse(req.body); if (result.success === false) { @@ -54,24 +55,14 @@ export const createParticipant = async (req: Request<{ pid: string }>, res: Resp { firstname: DataType.STRING, lastName: DataType.STRING, - groupId: DataType.UUID, + groupPid: DataType.UUID, + teamPid: 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({ @@ -79,8 +70,8 @@ export const createParticipant = async (req: Request<{ pid: string }>, res: Resp firstName: body.firstName, lastName: body.lastName, relevance: "MEMBER", - group: { connect: { pid: body.groupId } }, - team: { connect: { pid } }, + group: { connect: { pid: body.groupPid } }, + team: { connect: { pid: body.teamPid } }, }, select: returnedParticipant, }); @@ -93,16 +84,15 @@ export const createParticipant = async (req: Request<{ pid: string }>, res: Resp 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}'`)); + .json(generateError(`Could not link to team with ID '${body.teamPid}, or group with ID ${body.groupPid}'`)); } throw e; } }; +// at: PATCH api/participants/:pid/ export const updateParticipant = async (req: Request<{ pid: string }>, res: Response) => { - //insert TeamleaderAuth - - const result = ParticipantBody.partial().safeParse(req.body); // Should be partial, right? + const result = InitialParticipant.partial().safeParse(req.body); if (result.success === false) { return res.status(400).json( @@ -110,7 +100,7 @@ export const updateParticipant = async (req: Request<{ pid: string }>, res: Resp { firstname: DataType.STRING, lastName: DataType.STRING, - groupId: DataType.UUID, + groupPid: DataType.UUID, }, result.error ) @@ -126,7 +116,7 @@ export const updateParticipant = async (req: Request<{ pid: string }>, res: Resp data: { firstName: body.firstName, lastName: body.lastName, - group: { connect: { pid: body.groupId } }, + group: { connect: { pid: body.groupPid } }, }, select: returnedParticipant, }); @@ -144,9 +134,8 @@ export const updateParticipant = async (req: Request<{ pid: string }>, res: Resp } }; +// at: DELETE api/participants/:pid/ export const deleteParticipant = async (req: Request<{ pid: string }>, res: Response) => { - //insert TeamleaderAuth - const { pid } = req.params; try { @@ -155,7 +144,7 @@ export const deleteParticipant = async (req: Request<{ pid: string }>, res: Resp 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 new NotFoundError("participant", pid); } throw e; diff --git a/src/Controllers/role.controller.ts b/src/Controllers/role.controller.ts index feff325..353965d 100644 --- a/src/Controllers/role.controller.ts +++ b/src/Controllers/role.controller.ts @@ -8,6 +8,30 @@ import { createInsufficientPermissionsError, DataType, generateInvalidBodyError require("express-async-errors"); +const detailedRole = { + pid: true, + score: true, + schema: { + select: { + pid: true, + name: true, + }, + }, + participant: { + select: { + pid: true, + firstName: true, + lastName: true, + }, + }, + team: { + select: { + pid: true, + name: true, + }, + }, +}; + /** * * @param teamPid: Pid of the team to add the roles to @@ -67,8 +91,6 @@ export async function assignParticipantToRole(req: Request<{ pid: string }>, res 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 } } }, @@ -94,66 +116,3 @@ export async function assignParticipantToRole(req: Request<{ pid: string }>, res }, }); } - -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 } }, - }); -} diff --git a/src/Controllers/role_schema.controller.ts b/src/Controllers/role_schema.controller.ts index f47e154..f375451 100644 --- a/src/Controllers/role_schema.controller.ts +++ b/src/Controllers/role_schema.controller.ts @@ -20,7 +20,7 @@ const RoleSchemaBody = z.object({ schema: z.string(), }); -const UpdateRoleSchema = RoleSchemaBody.partial(); +const UpdateBody = RoleSchemaBody.partial(); const roleSchema = { pid: true, @@ -134,126 +134,48 @@ export const createRoleSchema = async ( } }; -export const updateRoleSchema = async (req: Request<{ pid: string }>, res: Response) => { +export const UpdateRoleSchema = async (req: Request<{ pid: string }>, res: Response) => { if (req.auth?.permission_level !== "ELEVATED") { res.status(403).json(createInsufficientPermissionsError()); } const { pid } = req.params; - const result = UpdateRoleSchema.safeParse(req.body); + const result = UpdateBody.safeParse(req.body); if (result.success === false) { return res.status(400).json( generateInvalidBodyError( { name: DataType.STRING, - schema: DataType.RESULT_SCHEMA, + schema: DataType.STRING, }, result.error ) ); } - const { name, schema } = result.data; - - const validatedSchema = parseSchema(schema); + const body = result.data; try { const schema = await prisma.roleSchema.update({ where: { pid }, data: { - name: name, - schema: validatedSchema, + name: body.name, + schema: body.schema, }, select: roleSchema, }); res.status(200).json({ type: "success", - payload: schema, + payload: { + schema, + }, }); } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { - throw new NotFoundError("roleSchema", pid); - } - - throw e; - } -}; - -export const deleteRoleSchema = async (req: Request<{ pid: string }>, res: Response) => { - if (req.auth?.permission_level !== "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { pid } = req.params; - - try { - await prisma.roleSchema.delete({ where: { pid } }); - - return res.status(204).end(); - } catch (e) { - if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { - return res.status(404).json(generateError(`The RoleSchema with the ID ${pid} could not be found`)); - } - - throw e; - } -}; - -interface visualParams { - schemaPid: string; -} - -interface visualBody { - mediaPid: string; -} - -export const addVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { schemaPid } = req.params; - - const schema = await prisma.roleSchema.update({ - where: { pid: schemaPid }, - data: { - visual: { connect: { pid: req.body.mediaPid } }, - }, - }); - - if (!schema) { - throw new NotFoundError("role_schema", schemaPid); - } - - return res.status(200).json({ - type: "success", - payload: {}, - }); -}; - -export const deleteVisual = async (req: Request, res: Response) => { - if (req.auth?.permission_level != "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { schemaPid, pid } = req.params; - - try { - await prisma.roleSchema.update({ - where: { - pid: schemaPid, - }, - data: { - visual: { disconnect: { pid } }, - }, - }); - return res.status(204).end(); - } catch (e) { - if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { - throw new NotFoundError("role_schema", schemaPid); + throw new NotFoundError("discipline", pid); } throw e; diff --git a/src/Controllers/team.controller.ts b/src/Controllers/team.controller.ts index 481a487..34e03dc 100644 --- a/src/Controllers/team.controller.ts +++ b/src/Controllers/team.controller.ts @@ -4,9 +4,31 @@ import { createInsufficientPermissionsError, DataType, generateInvalidBodyError import { requireLeaderOfTeam } from "../Middleware/auth/teamleaderAuth"; import { z } from "zod"; import { TeamBody } from "./user_auth.controller"; +import { Prisma } from "@prisma/client"; +import NotFoundError from "../Middleware/error/NotFoundError"; + +export const basicTeam = { + pid: true, + name: true, + discipline: { select: { pid: true } }, + roles: { + select: { + pid: true, + schema: { select: { name: true } }, + participant: { select: { pid: true } }, + }, + }, + participants: { + select: { + pid: true, + firstName: true, + lastName: true, + }, + }, +}; export const getTeams = async (req: Request, res: Response) => { - const teams = prisma.team.findMany({ select: { pid: true, name: true, disciplineId: true } }); + const teams = await prisma.team.findMany({ select: basicTeam }); res.status(200).json({ type: "success", payload: { teams } }); }; @@ -14,13 +36,9 @@ export const getTeams = async (req: Request, res: Response) => { export const getTeam = async (req: Request, res: Response) => { const { pid } = req.params; - const team = prisma.team.findUnique({ + const team = await prisma.team.findUnique({ where: { pid }, - select: { - disciplineId: true, - name: true, - pid: true, - }, + select: basicTeam, }); res.status(200).json({ type: "success", payload: { team } }); @@ -48,26 +66,35 @@ export const updateTeam = async (req: Request, res: Response) => { requireLeaderOfTeam(req.teamleader, body.pid); - const team = prisma.team.update({ - where: { - pid: body.pid, - }, - data: { - name: body.teamName, - discipline: { connect: { pid: body.disciplineId } }, - leaderEmail: body.leaderEmail, - }, - }); + try { + const team = await prisma.team.update({ + where: { + pid: body.pid, + }, + data: { + name: body.teamName, + discipline: { connect: { pid: body.disciplineId } }, + leaderEmail: body.leaderEmail, + }, + }); - res.status(204).json({ type: "success", payload: { team } }); + res.status(204).json({ type: "success", payload: { team } }); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError("team", body.pid); + } + + throw e; + } }; export const deleteTeam = async (req: Request, res: Response) => { const { pid } = req.params; + // TODO: accept admin auth requireLeaderOfTeam(req.teamleader, pid); - prisma.team.delete({ where: { pid } }); + await prisma.team.delete({ where: { pid } }); res.status(204).json({ type: "success", payload: { message: "Sucesfully deleted team" } }); }; diff --git a/src/Controllers/user_auth.controller.ts b/src/Controllers/user_auth.controller.ts index 51dd6a0..89824e4 100644 --- a/src/Controllers/user_auth.controller.ts +++ b/src/Controllers/user_auth.controller.ts @@ -7,6 +7,7 @@ import { createInsufficientPermissionsError, DataType, generateError, generateIn import { generateTeamleaderJWT, requireLeaderOfTeam } from "../Middleware/auth/teamleaderAuth"; import { createRolesForTeam } from "./role.controller"; import { any, z } from "zod"; +import { basicTeam } from "./team.controller"; export const TeamBody = z.object({ teamName: z.string().min(1), @@ -81,13 +82,13 @@ export const register = async (req: Request<{}, {}, CreateTeamBody>, res: Respon }, }); - //TODO: maybe use returned amount of created use? - createRolesForTeam(team.pid); + await createRolesForTeam(team.pid); const usid = nanoid(); (await mailClient).set(usid, team.pid); + // TODO: fix "eventname" verificationMail(req.body.leaderEmail, "eventname", usid); res.status(201).json({ type: "success", payload: { team } }); @@ -114,6 +115,7 @@ export const requestToken = async (req: Request, res: Response) => { (await mailClient).set(usid, team.pid); + // TODO: fix "eventname" verificationMail(team.leaderEmail, "eventname", usid); res.status(200).json({ type: "sucess", payload: { message: "Email sent!" } }); diff --git a/src/Middleware/error/defaultRoutes.ts b/src/Middleware/error/defaultRoutes.ts index 4a15997..0050727 100644 --- a/src/Middleware/error/defaultRoutes.ts +++ b/src/Middleware/error/defaultRoutes.ts @@ -5,7 +5,7 @@ export function notFoundHandler(req: Request, res: Response) { return res.status(404).json({ type: "error", payload: { - message: `The ${req.method} HTTP method is implemented for '${req.path}'`, + message: `The ${req.method} HTTP method is not implemented for '${req.path}'`, _links: [ { rel: "root", diff --git a/src/Routes/discipline.routes.ts b/src/Routes/discipline.routes.ts index 50a2de9..6111c64 100644 --- a/src/Routes/discipline.routes.ts +++ b/src/Routes/discipline.routes.ts @@ -1,10 +1,8 @@ import express from "express"; import eventRouter from "./event.routes"; import { - addVisual, createDiscipline, deleteDiscipline, - deleteVisual, getAllDisciplines, getDiscipline, updateDiscipline, @@ -20,18 +18,6 @@ router.get("/:pid", getDiscipline); router.patch("/:pid", requireAuthentication, updateDiscipline); router.delete<"/:pid", { pid: string }>("/:pid", requireAuthentication, deleteDiscipline); -router.post<"/:disciplinePid/images", { disciplinePid: string }>( - "/:disciplinePid/images", - requireAuthentication, - addVisual -); - -router.delete<"/:disciplinePid/images/:pid", { disciplinePid: string; pid: string }>( - "/:disciplinePid/images/:pid", - requireAuthentication, - deleteVisual -); - eventRouter.post("/:eventPid/disciplines", requireAuthentication, createDiscipline); export default router; diff --git a/src/Routes/event.routes.ts b/src/Routes/event.routes.ts index 5e68bd8..7c2d34d 100644 --- a/src/Routes/event.routes.ts +++ b/src/Routes/event.routes.ts @@ -2,9 +2,7 @@ import Express from "express"; import { string } from "zod"; import { addEvent, - addVisual, deleteEvent, - deleteVisual, getAllEvents, getEvent, updateEvent, @@ -22,12 +20,4 @@ router.patch<"/:pid/", { pid: string }>("/:pid/", requireAuthentication, updateE router.delete<"/:pid/", { pid: string }>("/:pid/", requireAuthentication, deleteEvent); -router.post<"/:eventPid/media", { eventPid: string }>("/:eventPid/media", requireAuthentication, addVisual); - -router.delete<"/:eventPid/media/:pid", { eventPid: string; pid: string }>( - "/:eventPid/media/:pid", - requireAuthentication, - deleteVisual -); - export default router; diff --git a/src/Routes/media.routes.ts b/src/Routes/media.routes.ts index f340de1..e4c633a 100644 --- a/src/Routes/media.routes.ts +++ b/src/Routes/media.routes.ts @@ -1,7 +1,7 @@ import express from "express"; import fileUpload from "express-fileupload"; import { requireAuthentication } from "../Middleware/auth/auth"; -import { deleteMedia, getAllMedia, getMediaMeta, uploadImage } from "../Controllers/media.controller"; +import { deleteMedia, getAllMedia, getMediaMeta, linkMedia, unlinkMedia, uploadImage } from "../Controllers/media.controller"; import eventRouter from "./event.routes"; import disciplineRouter from "./discipline.routes"; import roleSchemaRouter from "./role_schema.routes"; @@ -19,4 +19,28 @@ router.get("/:pid/meta", getMediaMeta); router.delete("/:pid", requireAuthentication, deleteMedia); +eventRouter.post<"/:pid/media", { pid: string }>( + "/:pid/media", requireAuthentication, linkMedia +); + +eventRouter.delete<"/:pid/media/:mediaPid", { pid: string, mediaPid: string }>( + "/:pid/media/:mediaPid", requireAuthentication, unlinkMedia +); + +disciplineRouter.post<"/:pid/media", { pid: string }>( + "/:pid/media", requireAuthentication, linkMedia +); + +disciplineRouter.delete<"/:pid/media/:mediaPid", { pid: string, mediaPid: string }>( + "/:pid/media/:mediaPid", requireAuthentication, unlinkMedia +); + +roleSchemaRouter.post<"/:pid/media", { pid: string }>( + "/:pid/media", requireAuthentication, linkMedia +); + +roleSchemaRouter.delete<"/:pid/media/:mediaPid", { pid: string, mediaPid: string }>( + "/:pid/media/:mediaPid", requireAuthentication, unlinkMedia +); + export default router; diff --git a/src/Routes/participant.routes.ts b/src/Routes/participant.routes.ts index 21937a2..7ba300f 100644 --- a/src/Routes/participant.routes.ts +++ b/src/Routes/participant.routes.ts @@ -1,29 +1,24 @@ import express from "express"; -import teamRouter from "./team.routes"; import { createParticipant, deleteParticipant, updateParticipant } from "../Controllers/participant.controller"; -import { requireAuthentication } from "../Middleware/auth/auth"; -import { requireTeamleaderAuthentication } from "../Middleware/auth/teamleaderAuth"; +import { requireAuthentication, requireConfiguredAuthentication } from "../Middleware/auth/auth"; const router = express.Router(); -teamRouter.post<"/:pid/participant/", { pid: string }>( - "/:pid/participant/", - requireAuthentication, - requireTeamleaderAuthentication, +router.post( + "/", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), createParticipant ); -teamRouter.patch<"/:pid/participant/", { pid: string }>( - "/:pid/participant/", - requireAuthentication, - requireTeamleaderAuthentication, +router.patch<"/:pid/", { pid: string }>( + "/:pid/", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), updateParticipant ); -teamRouter.delete<"/:pid/participant/", { pid: string }>( - "/:pid/participant/", - requireAuthentication, - requireTeamleaderAuthentication, +router.delete<"/:pid/", { pid: string }>( + "/:pid/", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), deleteParticipant ); diff --git a/src/Routes/role.routes.ts b/src/Routes/role.routes.ts index 5bbd03e..0ad57ce 100644 --- a/src/Routes/role.routes.ts +++ b/src/Routes/role.routes.ts @@ -1,9 +1,20 @@ import Express from "express"; import { assignParticipantToRole, getRolesForTeam } from "../Controllers/role.controller"; +import { requireAuthentication, requireConfiguredAuthentication } from "../Middleware/auth/auth"; const router = Express.Router(); //TO DO: maybe transfer getRolesForTeam to team router -> Seconded -router.get<"team/:teamPid/", { teamPid: string }>("team/:teamPid/", getRolesForTeam); +router.get<"team/:teamPid/", { teamPid: string }>( + "team/:teamPid/", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), + getRolesForTeam +); -router.put<"/:pid/participant", { pid: string }>("/:pid/participant", assignParticipantToRole); +router.put<"/:pid/participant", { pid: string }>( + "/:pid/participant", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), + assignParticipantToRole +); + +export default router; diff --git a/src/Routes/role_schema.routes.ts b/src/Routes/role_schema.routes.ts index f6ada5f..688dafc 100644 --- a/src/Routes/role_schema.routes.ts +++ b/src/Routes/role_schema.routes.ts @@ -1,9 +1,7 @@ import express from "express"; import disciplineRouter from "./discipline.routes"; import { - addVisual, createRoleSchema, - deleteVisual, getAllRoleSchemas, getAllRoleSchemasWithParam, getRoleSchema, @@ -20,12 +18,4 @@ disciplineRouter.get("/:disciplinePid/role-schemas", getAllRoleSchemasWithParam) disciplineRouter.post("/:disciplinePid/role-schemas", requireAuthentication, createRoleSchema); -router.post<"/:schemaPid/images", { schemaPid: string }>("/:schemaPid/images", requireAuthentication, addVisual); - -router.delete<"/:schemaPid/images/:pid", { schemaPid: string; pid: string }>( - "/:schemaPid/images/:pid", - requireAuthentication, - deleteVisual -); - export default router; diff --git a/src/Routes/team.routes.ts b/src/Routes/team.routes.ts index a12afb1..a8ffebe 100644 --- a/src/Routes/team.routes.ts +++ b/src/Routes/team.routes.ts @@ -6,9 +6,17 @@ import { deleteTeam, getTeam, getTeams, updateTeam } from "../Controllers/team.c const router = express.Router(); router.get("/", requireConfiguredAuthentication({ type: "admin", optional: false }), getTeams); -router.get("/:id", getTeam); +router.get( + "/:id", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), + getTeam +); router.put("/", requireTeamleaderAuthentication, updateTeam); -router.delete<"/:pid/", { pid: string }>("/:pid/", requireAuthentication, requireTeamleaderAuthentication, deleteTeam); +router.delete<"/:pid/", { pid: string }>( + "/:pid/", + requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }), + deleteTeam +); export default router; diff --git a/src/app.ts b/src/app.ts index 7a41c6c..d41ffba 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,7 +14,9 @@ import logger from "./Middleware/error/logger"; import debugLogger from "./Middleware/debug/logger"; import mediaRouter from "./Routes/media.routes"; import userRouter from "./Routes/user_auth.routes"; -import TeamRouter from "./Routes/team.routes"; +import teamRouter from "./Routes/team.routes"; +import roleRouter from "./Routes/role.routes"; +import participantRouter from "./Routes/participant.routes"; import { notFoundHandler, rootHandler } from "./Middleware/error/defaultRoutes"; // Set up async error handling @@ -87,7 +89,11 @@ async function main() { app.use("/api/users", userRouter); - app.use("/api/teams", TeamRouter); + app.use("/api/teams", teamRouter); + + app.use("/api/roles", roleRouter); + + app.use("/api/participants", participantRouter); app.get("/", rootHandler); app.get("/api", rootHandler); @@ -95,9 +101,6 @@ async function main() { // Error handling app.use(defaultErrorHandler); // This has to be the LAST ROUTE - // Disable the media router for now - // app.use("/api/media", mediaRouter); - app.use(notFoundHandler); app.listen(process.env.PORT, () => { diff --git a/src/lib/mail.ts b/src/lib/mail.ts index c55fd8e..7c96463 100644 --- a/src/lib/mail.ts +++ b/src/lib/mail.ts @@ -66,6 +66,7 @@ const sendMail = async (from: string, to: string, subject: string, text?: string export const verificationMail = async (to: string, eventName: string, verificationLink: string) => { const raw = mjml.getTemplate("emailVerification"); + // TODO: the process.env.DOMAIN is undefined in Development mode !! verificationLink = "https://" + ("api." + process.env.DOMAIN ?? "localhost:3000/api") + "/users/verify/" + verificationLink; const message = Handlebars.compile(raw);