diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 50bae8e..1d07b9e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,13 +34,11 @@ model Admin { } model Discipline { - 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 + id Int @id @default(autoincrement()) + pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + name String + minTeamSize Int + maxTeamSize Int roles RoleSchema[] teams Team[] @@ -62,15 +60,15 @@ model RoleSchema { } model Team { - id Int @id @default(autoincrement()) - pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + id Int @id @default(autoincrement()) + pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid name String leaderEmail String verified Boolean @default(false) - roles Role[] @relation(name: "participants") + roles Role[] @relation(name: "participants") participants Participant[] - discipline Discipline @relation(fields: [disciplineId], references: [id], onDelete: Cascade) + discipline Discipline @relation(fields: [disciplineId], references: [id], onDelete: Cascade) disciplineId Int } @@ -83,7 +81,7 @@ model Participant { group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) groupId Int - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) teamId Int roles Role[] } @@ -127,7 +125,7 @@ model Group { model Media { id Int @id @default(autoincrement()) pid String @unique - description String @default("visual") + description String events Event[] disciplines Discipline[] diff --git a/src/Controllers/discipline.controller.ts b/src/Controllers/discipline.controller.ts index 6251a22..40d5494 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, @@ -36,8 +36,6 @@ 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; @@ -130,90 +128,10 @@ export const getDiscipline = async (req: Request, res: }); }; -export const updateDiscipline = async (req: Request<{ pid: string }>, res: Response) => { - if (req.auth?.permission_level !== "ELEVATED") { - res.status(403).json(createInsufficientPermissionsError()); - } - - const { pid } = req.params; - - const result = UpdateDisciplineBody.safeParse(req.body); - - if (result.success === false) { - return res.status(400).json( - generateInvalidBodyError({ - name: DataType.STRING, - minTeamSize: DataType.NUMBER, - maxTeamSize: DataType.NUMBER, - briefDescription: DataType.STRING, - ["fullDescription?"]: DataType.STRING, - }) - ); - } - - const body = result.data; - - try { - const discipline = await prisma.discipline.update({ - where: { pid }, - data: { - name: body.name, - minTeamSize: body.minTeamSize, - maxTeamSize: body.maxTeamSize, - briefDescription: body.briefDescription, - fullDescription: body.fullDescription, - }, - select: { - pid: true, - name: true, - minTeamSize: true, - maxTeamSize: true, - briefDescription: true, - fullDescription: true, - }, - }); - - if (!discipline) { - throw new NotFoundError("discipline", pid); - } - - res.status(200).json({ - type: "success", - payload: { - discipline, - }, - }); - } 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, - }, - }, - }); - } - - throw e; - } -}; - interface CreateDisciplineBody { name?: string; minTeamSize?: number; maxTeamSize?: number; - briefDescription?: string; - fullDescription?: string; } // require: auth(ELEVATED) @@ -323,3 +241,63 @@ 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 1e558c7..03d0426 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 e, { Request, Response } from "express"; +import { Request, Response } from "express"; import { z } from "zod"; import prisma from "../lib/prisma"; import NotFoundError from "../Middleware/error/NotFoundError"; @@ -8,39 +8,16 @@ import { createInsufficientPermissionsError, DataType, generateError, generateIn require("express-async-errors"); -const EventBody = z.object({ - name: z.string(), - date: z.string(), - briefDescription: z.string(), - fullDescription: z.string(), -}); - -const UpdateBody = EventBody.partial(); - -const CreateEventBody = EventBody.partial({ - fullDescription: true, -}); - export const getAllEvents = async (req: Request, res: Response) => { const events = await prisma.event.findMany({ select: { - pid: true, name: true, - date: true, briefDescription: true, fullDescription: true, visual: { select: { pid: true, description: true } }, - disciplines: { - select: { - pid: true, - name: true, - }}, - organisations: { - select: { - pid: true, - name: true, - } - } + date: true, + pid: true, + id: false, }, }); @@ -69,25 +46,13 @@ export const getEvent = async (req: Request, res: Response) => { pid: eventId, }, select: { - pid: true, name: true, - date: true, briefDescription: true, fullDescription: true, + date: true, + pid: true, + id: false, visual: { select: { pid: true, description: true } }, - disciplines: { - select: { - pid: true, - name: true, - briefDescription: true, - fullDescription: true, - }}, - organisations: { - select: { - pid: true, - name: true, - } - } }, }); @@ -131,10 +96,12 @@ export const addEvent = async (req: Request, res: Response) => { if (req.auth?.permission_level !== "ELEVATED") { res.status(403).json(createInsufficientPermissionsError()); } - - const result = CreateEventBody.safeParse(req.body); - - if(result.success === false){ + if ( + typeof req.body.name !== "string" || + typeof req.body.date !== "string" || + typeof req.body.briefDescription !== "string" || + (req.body.fullDescription && typeof req.body.fullDescription !== "string") + ) { return res.status(400).json( generateInvalidBodyError({ name: DataType.STRING, @@ -155,9 +122,10 @@ export const addEvent = async (req: Request, res: Response) => { fullDescription: req.body.fullDescription, }, select: { - pid: true, name: true, date: true, + pid: true, + id: false, briefDescription: true, fullDescription: true, }, @@ -171,6 +139,15 @@ 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()); @@ -273,3 +250,67 @@ export const deleteEvent = async (req: Request, res: Res 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 e; + } +}; diff --git a/src/Controllers/media.controller.ts b/src/Controllers/media.controller.ts index 22802a7..eb78bc3 100644 --- a/src/Controllers/media.controller.ts +++ b/src/Controllers/media.controller.ts @@ -1,4 +1,4 @@ -import { NextFunction, Request, response, Response } from "express"; +import { Request, response, Response } from "express"; import fs from "fs"; import isSvg from "is-svg"; import { fromBuffer as fileTypeFromBuffer } from "file-type"; @@ -12,8 +12,6 @@ import { type } from "os"; import { unlink } from "fs/promises"; import ForwardableError from "../Middleware/error/ForwardableError"; import SchemaError from "../Middleware/error/SchemaError"; -import { z } from "zod"; -import { updateEvent } from "./event.controller"; require("express-async-errors"); @@ -191,87 +189,3 @@ export const deleteMedia = async (req: Request, res: Response) => { throw e; } }; - -//TODO: maybe create a function that adds the tableToUpdate based on path -// and call it before calling (un)linkMedia - -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") { - console.log("not found error"); - throw new NotFoundError(tableToUpdate[2], pid); - } - 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, - }, - }, - }); - } - - 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/role_schema.controller.ts b/src/Controllers/role_schema.controller.ts index 8b05caf..f47e154 100644 --- a/src/Controllers/role_schema.controller.ts +++ b/src/Controllers/role_schema.controller.ts @@ -133,3 +133,129 @@ export const createRoleSchema = async ( throw e; } }; + +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); + + if (result.success === false) { + return res.status(400).json( + generateInvalidBodyError( + { + name: DataType.STRING, + schema: DataType.RESULT_SCHEMA, + }, + result.error + ) + ); + } + + const { name, schema } = result.data; + + const validatedSchema = parseSchema(schema); + + try { + const schema = await prisma.roleSchema.update({ + where: { pid }, + data: { + name: name, + schema: validatedSchema, + }, + select: roleSchema, + }); + + res.status(200).json({ + type: "success", + 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 e; + } +}; diff --git a/src/Routes/discipline.routes.ts b/src/Routes/discipline.routes.ts index 6111c64..50a2de9 100644 --- a/src/Routes/discipline.routes.ts +++ b/src/Routes/discipline.routes.ts @@ -1,8 +1,10 @@ import express from "express"; import eventRouter from "./event.routes"; import { + addVisual, createDiscipline, deleteDiscipline, + deleteVisual, getAllDisciplines, getDiscipline, updateDiscipline, @@ -18,6 +20,18 @@ 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 7c2d34d..5e68bd8 100644 --- a/src/Routes/event.routes.ts +++ b/src/Routes/event.routes.ts @@ -2,7 +2,9 @@ import Express from "express"; import { string } from "zod"; import { addEvent, + addVisual, deleteEvent, + deleteVisual, getAllEvents, getEvent, updateEvent, @@ -20,4 +22,12 @@ 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 e4c633a..f340de1 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, linkMedia, unlinkMedia, uploadImage } from "../Controllers/media.controller"; +import { deleteMedia, getAllMedia, getMediaMeta, uploadImage } from "../Controllers/media.controller"; import eventRouter from "./event.routes"; import disciplineRouter from "./discipline.routes"; import roleSchemaRouter from "./role_schema.routes"; @@ -19,28 +19,4 @@ 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/role_schema.routes.ts b/src/Routes/role_schema.routes.ts index 688dafc..f6ada5f 100644 --- a/src/Routes/role_schema.routes.ts +++ b/src/Routes/role_schema.routes.ts @@ -1,7 +1,9 @@ import express from "express"; import disciplineRouter from "./discipline.routes"; import { + addVisual, createRoleSchema, + deleteVisual, getAllRoleSchemas, getAllRoleSchemasWithParam, getRoleSchema, @@ -18,4 +20,12 @@ 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/app.ts b/src/app.ts index 9121346..7a41c6c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -95,6 +95,9 @@ 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, () => {