From bded472d05ce04a84ff31da7021c5f98d60c37ae Mon Sep 17 00:00:00 2001 From: Laurin <60652077+Flexla54@users.noreply.github.com> Date: Fri, 13 May 2022 13:21:16 +0200 Subject: [PATCH] Added basic implementation of media --- prisma/schema.prisma | 20 ++--- src/Controllers/discipline.controller.ts | 58 +++++++++++++++ src/Controllers/event.controller.ts | 69 ++++++++++++++++- src/Controllers/media.controller.ts | 91 ++++++++++++++++++++++- src/Controllers/role_schema.controller.ts | 58 +++++++++++++++ src/Routes/discipline.routes.ts | 8 ++ src/Routes/event.routes.ts | 7 +- src/Routes/media.routes.ts | 6 +- src/Routes/role_schema.routes.ts | 9 +++ 9 files changed, 307 insertions(+), 19 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e5bb5f0..9557922 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,11 +10,12 @@ generator client { } model Event { - id Int @id @default(autoincrement()) // Primary key - pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid // Public key - date DateTime - name String - description String + id Int @id @default(autoincrement()) // Primary key + pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid // Public key + date DateTime + name String + briefDescription String + fullDescription String? disciplines Discipline[] organisations Organisation[] @@ -119,13 +120,12 @@ model Group { model Media { id Int @id @default(autoincrement()) - pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + pid String @unique description String - location String @unique - event Event[] - discipline Discipline[] - role RoleSchema[] + events Event[] + disciplines Discipline[] + roles RoleSchema[] } enum AdminLevel { diff --git a/src/Controllers/discipline.controller.ts b/src/Controllers/discipline.controller.ts index 93431ae..d6451bb 100644 --- a/src/Controllers/discipline.controller.ts +++ b/src/Controllers/discipline.controller.ts @@ -187,3 +187,61 @@ export const deleteDiscipline = async (req: Request 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 } } + } + }); + + if (!discipline) { + throw new NotFoundError("discipline", disciplinePid); + } + + return res.status(200).json({ + type: "success", + payload: { discipline } + }) +}; + +export const deleteVisual = async (req: Request, res: Response) => { + if (req.auth?.permission_level != "ELEVATED"){ + res.status(403).json(createInsufficientPermissionsError()); + } + + const { disciplinePid } = req.params; + + try { + await prisma.discipline.update({ + where: { + pid: disciplinePid, + }, + data: { + visual: { disconnect: { pid: req.body.mediaPid } } + } + }); + return res.status(204).end(); + } catch (e) { + if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError("discipline", disciplinePid); + } + + throw e; + } +}; diff --git a/src/Controllers/event.controller.ts b/src/Controllers/event.controller.ts index 99378fa..6e9ba7c 100644 --- a/src/Controllers/event.controller.ts +++ b/src/Controllers/event.controller.ts @@ -2,13 +2,15 @@ import { Prisma } from "@prisma/client"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime"; import { Request, Response } from "express"; import prisma from "../lib/prisma"; +import NotFoundError from "../Middleware/error/NotFoundError"; import { createInsufficientPermissionsError, DataType, generateError, generateInvalidBodyError } from "./common"; export const getAllEvents = async (req: Request, res: Response) => { const events = await prisma.event.findMany({ select: { name: true, - description: true, + briefDescription: true, + fullDescription: true, date: true, pid: true, id: false, @@ -41,7 +43,8 @@ export const getEvent = async (req: Request, res: Response) => { }, select: { name: true, - description: true, + briefDescription: true, + fullDescription: true, date: true, pid: true, id: false, @@ -102,14 +105,14 @@ export const addEvent = async (req: Request, res: Response) => { data: { name: req.body.name, date: req.body.date, - description: req.body.description, + briefDescription: req.body.description, }, select: { name: true, date: true, pid: true, id: false, - description: true, + briefDescription: true, }, }); @@ -145,3 +148,61 @@ export const deleteEvent = (req: Request, res: Response) throw e; } }; + +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; + + 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: { event } + }) +}; + +export const deleteVisual = async (req: Request, res: Response) => { + if (req.auth?.permission_level != "ELEVATED"){ + res.status(403).json(createInsufficientPermissionsError()); + } + + const { eventPid } = req.params; + + try { + await prisma.event.update({ + where: { + pid: eventPid, + }, + data: { + visual: { disconnect: { pid: req.body.mediaPid } } + } + }); + 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 c19314c..2083ef2 100644 --- a/src/Controllers/media.controller.ts +++ b/src/Controllers/media.controller.ts @@ -1,8 +1,16 @@ -import { Request, 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"; import { AUTH_ERROR, createError, createInsufficientPermissionsError } from "./common"; +import { Prisma } from "@prisma/client"; +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" + function createMediaLinks(fileName: string) { return [{ rel: "self", type: "GET", href: `/api/media/${fileName}` }]; @@ -31,6 +39,14 @@ export const uploadImage = async (req: Request, res: Response) => { }); } + if ( typeof req.body.description !== "string") { + return res.status(400).json( + generateInvalidBodyError({ + description: DataType.STRING + }) + ); + } + const file = req.files.file; if (Array.isArray(file)) { @@ -69,11 +85,80 @@ export const uploadImage = async (req: Request, res: Response) => { file.mv("media/" + fileName, console.error); + //generate record + const media = await prisma.media.create({ + data: { + pid: fileName, + description: req.body.description, + }, + select: { + pid: true, + description: true, + } + }) + return res.status(201).json({ type: "success", + payload: { media } + }) +}; + +export const getAllMedia = async (req: Request, res: Response) => { + const medias = await prisma.media.findMany({ + select: { + description: true, + events: true, + disciplines: true, + roles: true, + pid: true, + id: false, + }, + }); + + res.status(200).json({ + type: "success", payload: { - message: "The file was uploaded and created on the server", + medias, }, - _links: createMediaLinks(fileName), }); }; + +interface CreateMediaBody { + description?: string; + location?: string; + + events?: string[]; + disciplines?: string[]; + roles?: string[]; +} + +interface MediaParams { + eventPid?: string; + disciplinePid?: string; + rolePid?: string; +} + +export const deleteMedia = async (req: Request, res: Response) => { + if (req.auth?.permission_level != "ELEVATED"){ + res.status(403).json(createInsufficientPermissionsError()); + } + + const { pid } = req.params; + + const location = "media/" + pid; + + if (fs.existsSync(location)) { + await unlink(location); + } + + try { + await prisma.discipline.delete({ where: { pid }}); + return res.status(204).end(); + } catch (e) { + if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { + throw new NotFoundError("discipline", pid); + } + + throw e; + } +}; \ No newline at end of file diff --git a/src/Controllers/role_schema.controller.ts b/src/Controllers/role_schema.controller.ts index ca73554..2f03949 100644 --- a/src/Controllers/role_schema.controller.ts +++ b/src/Controllers/role_schema.controller.ts @@ -123,3 +123,61 @@ export const createRoleSchema = async ( 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: { schema } + }) +}; + +export const deleteVisual = async (req: Request, res: Response) => { + if (req.auth?.permission_level != "ELEVATED"){ + res.status(403).json(createInsufficientPermissionsError()); + } + + const { schemaPid } = req.params; + + try { + await prisma.roleSchema.update({ + where: { + pid: schemaPid, + }, + data: { + visual: { disconnect: { pid: req.body.mediaPid } } + } + }); + 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 08aec30..acc2d76 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, } from "../Controllers/discipline.controller"; @@ -11,9 +13,15 @@ import { requireAuthentication } from "../Middleware/auth/auth"; const router = express.Router(); router.get("/", getAllDisciplines); // TODO: Optional auth + router.get("/:pid", getDiscipline); + 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 2ce9e38..290816d 100644 --- a/src/Routes/event.routes.ts +++ b/src/Routes/event.routes.ts @@ -1,5 +1,6 @@ import Express from "express"; -import { addEvent, getAllEvents, getEvent } from "../Controllers/event.controller"; +import { string } from "zod"; +import { addEvent, addVisual, deleteVisual, getAllEvents, getEvent } from "../Controllers/event.controller"; import { requireAuthentication } from "../Middleware/auth/auth"; const router = Express.Router(); @@ -9,4 +10,8 @@ router.get("/:eventId", getEvent); router.post("/", requireAuthentication, addEvent); +router.post<"/:eventPid/images", {eventPid: string}>("/:eventPid/images", requireAuthentication, addVisual); + +router.delete<"/:eventPid/images/:pid", {eventPid: string, pid: string}>("/:eventPid/images/:pid", requireAuthentication, deleteVisual); + export default router; diff --git a/src/Routes/media.routes.ts b/src/Routes/media.routes.ts index e58b490..9d781e5 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 { uploadImage } from "../Controllers/media.controller"; +import { deleteMedia, getAllMedia, uploadImage } from "../Controllers/media.controller"; const router = express.Router(); @@ -10,4 +10,8 @@ router.post("/", requireAuthentication, fileUpload(), uploadImage); // Media storage with express static (Should only handle GET and HEAD request methods) router.use("/", express.static("media", { redirect: false })); +router.get("/:pid", getAllMedia); + +router.delete("/:pid", requireAuthentication, deleteMedia); + export default router; diff --git a/src/Routes/role_schema.routes.ts b/src/Routes/role_schema.routes.ts index ea3c938..0ec3f67 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, @@ -11,9 +13,16 @@ import { requireAuthentication } from "../Middleware/auth/auth"; const router = express.Router(); router.get("/", getAllRoleSchemas); + router.get("/:pid", getRoleSchema); 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;