diff --git a/src/index.ts b/src/index.ts index c1dba75c960761107c50fda0d213144ccdef0705..64167195779a9b3041cd1e590563a670be41b6f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import expressSession from "express-session"; import filesRoute from "./routes/files"; import mongoose from "mongoose"; import morgan from "morgan"; +import multer from "multer"; import newsRoute from "./routes/news"; import notificationsRoute from "./routes/notifications"; import termsRoute from "./routes/terms"; @@ -81,6 +82,8 @@ app.use((err: any, req: Request, res: Response, next: NextFunction) => { if (err instanceof ErrorHandler) return handleError(err, res); if (err instanceof mongoose.Error.CastError) return handleError(new ErrorHandler(400, err.message), res); + if (err instanceof multer.MulterError) + return handleError(new ErrorHandler(400, err.message), res); //Flush out the stack to the console console.error(err.stack); diff --git a/src/middlewares/files/createFile.ts b/src/middlewares/files/createFile.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e0028ac3643991a0ee811feb303b590fed31599 --- /dev/null +++ b/src/middlewares/files/createFile.ts @@ -0,0 +1,41 @@ +import { NextFunction, Request, Response } from "express"; + +import { ErrorHandler } from "../utils/ErrorHandler"; +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import fs from "fs"; + +/** + * Upload a new profile picture + */ +const createFile = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const newFile = new File(); + + newFile.originalName = req.file.originalname; + newFile.uploadDate = new Date(); + newFile.path = req.file.path; + newFile.mimeType = req.file.mimetype; + try { + await newFile.save(); + } catch (e) { + fs.unlinkSync(req.file.path); + throw new ErrorHandler( + 500, + "There was an error while recording the file in the Database!" + ); + } + + res.data.value = newFile._id; + + next(); + } catch (err) { + next(err); + } +}; + +export default createFile; diff --git a/src/middlewares/files/handleFileValidationError.ts b/src/middlewares/files/handleFileValidationError.ts deleted file mode 100644 index 0bdf0f873267ba1f3c6fd0e362c828a42654336d..0000000000000000000000000000000000000000 --- a/src/middlewares/files/handleFileValidationError.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NextFunction, Request, Response } from "express"; - -/** - * Middleware that handles errors after uploading a file - */ -const handleFileValidationError = () => async ( - req: Request, - res: Response, - next: NextFunction -) => { - if (req.fileValidationError) { - return res.status(400).send(req.fileValidationError); - } - if (!req.file) { - return res.status(400).send("File wasn't provided!"); - } - next(); -}; - -export default handleFileValidationError; diff --git a/src/middlewares/files/fileFilter.ts b/src/middlewares/files/imageFilter.ts similarity index 54% rename from src/middlewares/files/fileFilter.ts rename to src/middlewares/files/imageFilter.ts index 28c4e66ad3c45e98d5778db42d65be0df4f95968..4e5d888508eab5cdd67d1cf5d7f52a5bc745389b 100644 --- a/src/middlewares/files/fileFilter.ts +++ b/src/middlewares/files/imageFilter.ts @@ -1,20 +1,20 @@ -import multer, { FileFilterCallback, Multer } from "multer"; +import { ErrorHandler } from "../utils/ErrorHandler"; +import { FileFilterCallback } from "multer"; /** - * multer fileFilter that only lets png or jpeg to be uploaded + * multer imageFilter that only lets png or jpeg to be uploaded * * @param {Express.Request} req * @param {Express.Multer.File} file * @param {FileFilterCallback} cb */ -export function fileFilter( +export function imageFilter( req: Express.Request, file: Express.Multer.File, cb: FileFilterCallback ) { if (file.mimetype !== "image/png" && file.mimetype !== "image/jpeg") { - req.fileValidationError = "Invalid file type!"; - return cb(null, false); + return cb(new ErrorHandler(400, "Invalid file type!")); } else { cb(null, true); } diff --git a/src/middlewares/files/imageStorage.ts b/src/middlewares/files/imageStorage.ts new file mode 100644 index 0000000000000000000000000000000000000000..abf680cee2f1e4514a44013ff37acdd40a4d8ffd --- /dev/null +++ b/src/middlewares/files/imageStorage.ts @@ -0,0 +1,25 @@ +import multer from "multer"; +/** + * Multer DiskStorage configuration + * @param location where to save the file + */ +export const imageStorage = (location: string) => + multer.diskStorage({ + destination: function (req, file, callback) { + callback(null, location); + }, + filename: function (req, file, callback) { + const fileExtension = file.originalname.substring( + file.originalname.lastIndexOf(".") + ); + callback( + null, + Date.now() + + "-" + + Math.random().toString(36).substring(7) + + fileExtension + ); + }, + }); + +export default imageStorage; diff --git a/src/middlewares/files/profile/getProfilePicture.ts b/src/middlewares/files/profile/getProfilePicture.ts deleted file mode 100644 index 660beab170d280d365f7530fe6319e611e9bf865..0000000000000000000000000000000000000000 --- a/src/middlewares/files/profile/getProfilePicture.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NextFunction, Request, Response } from "express"; - -import File from "../../../models/FileSchema"; - -/** - * Get Profile picture file from res.data.profile - */ -const getProfilePicture = () => async ( - req: Request, - res: Response, - next: NextFunction -) => { - try { - if (!res.data.profile!.pictureId) - return res - .status(400) - .send({ message: "You dont have a profile picture!" }); - - const file = await File.findById(res.data.profile!.pictureId).lean().exec(); - return res.sendFile(file!.path, { root: "." }); - } catch (err) { - next(err); - } -}; - -export default getProfilePicture; diff --git a/src/middlewares/files/profile/profilePictureStorage.ts b/src/middlewares/files/profile/profilePictureStorage.ts deleted file mode 100644 index 3ce61b9ee3e42f73925838a9760492c9c2bb48e1..0000000000000000000000000000000000000000 --- a/src/middlewares/files/profile/profilePictureStorage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import multer from "multer"; -import path from "path"; - -export const profilePictureStorage = multer.diskStorage({ - destination: function (req, file, callback) { - callback(null, "uploads/profile_pictures/"); - }, - filename: function (req, file, callback) { - const fileExtension = file.originalname.substring( - file.originalname.lastIndexOf(".") - ); - callback( - null, - Date.now() + "-" + Math.random().toString(36).substring(7) + fileExtension - ); - }, -}); - -export default profilePictureStorage; diff --git a/src/middlewares/files/profile/uploadProfilePicture.ts b/src/middlewares/files/profile/uploadProfilePicture.ts deleted file mode 100644 index d18667298cf4cde82b167572556ab8c5ed4d0e1c..0000000000000000000000000000000000000000 --- a/src/middlewares/files/profile/uploadProfilePicture.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NextFunction, Request, Response } from "express"; - -import { ErrorHandler } from "../../../middlewares/utils/ErrorHandler"; -import File from "../../../models/FileSchema"; -import Profile from "../../../models/ProfileSchema"; -import fs from "fs"; - -/** - * Upload a new profile picture to a given User in res.data.profile - */ -const uploadProfilePicture = () => async ( - req: Request, - res: Response, - next: NextFunction -) => { - try { - if (!res.data.profile) throw new ErrorHandler(400, "User not found!"); - if (res.data.profile.pictureId) { - const oldFile = await File.findByIdAndRemove(res.data.profile.pictureId) - .lean() - .exec(); - if (oldFile) await fs.unlinkSync(oldFile!.path); - } - - const newFile = new File(); - - newFile.originalName = req.file.originalname; - newFile.uploadDate = new Date(); - newFile.path = req.file.path; - newFile.mimeType = req.file.mimetype; - - await newFile.save(); - - await Profile.updateOne( - { _id: res.data.profile!._id }, - { pictureId: newFile._id } - ).exec(); - - return res.status(200).send(); - } catch (err) { - next(err); - } -}; - -export default uploadProfilePicture; diff --git a/src/middlewares/files/sendFile.ts b/src/middlewares/files/sendFile.ts new file mode 100644 index 0000000000000000000000000000000000000000..4103093f4b45dbd7dfc611331112c29b3bcb1d28 --- /dev/null +++ b/src/middlewares/files/sendFile.ts @@ -0,0 +1,25 @@ +import { NextFunction, Request, Response } from "express"; + +import { ErrorHandler } from "../utils/ErrorHandler"; +import File from "../../models/FileSchema"; + +/** + * Return the file with id = res.data.value + */ +const sendFile = (): any => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const file = await File.findById(res.data.value).lean().exec(); + + if (!file) throw new ErrorHandler(404, "File not found!"); + + return res.sendFile(file.path, { root: "." }); + } catch (err) { + next(err); + } +}; + +export default sendFile; diff --git a/src/middlewares/files/updateProfilePicture.ts b/src/middlewares/files/updateProfilePicture.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e432d01e21e0e3aa25de690ee497933ff100286 --- /dev/null +++ b/src/middlewares/files/updateProfilePicture.ts @@ -0,0 +1,34 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import fs from "fs"; + +/** + * Upload a new profile picture + */ +const updateProfilePicture = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + if (res.data.profile!.picture) { + const oldFile = await File.findByIdAndRemove(res.data.profile!.picture) + .lean() + .exec(); + if (oldFile) fs.unlinkSync(oldFile!.path); + } + + await Profile.updateOne( + { _id: res.data.profile!._id }, + { picture: res.data.value! } + ).exec(); + + next(); + } catch (err) { + next(err); + } +}; + +export default updateProfilePicture; diff --git a/src/middlewares/files/updateTermCardBg.ts b/src/middlewares/files/updateTermCardBg.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e7e5c0cb565d86bc662f2438d35b27d1cf0f0ef --- /dev/null +++ b/src/middlewares/files/updateTermCardBg.ts @@ -0,0 +1,37 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; +import fs from "fs"; + +/** + * Upload a new profile picture + */ +const uploadTermCardBg = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + if (res.data.term!.backgroundFile) { + const oldFile = await File.findByIdAndRemove( + res.data.term!.backgroundFile + ) + .lean() + .exec(); + if (oldFile) fs.unlinkSync(oldFile!.path); + } + + await Term.updateOne( + { _id: res.data.term!._id }, + { backgroundFile: res.data.value! } + ).exec(); + + next(); + } catch (err) { + next(err); + } +}; + +export default uploadTermCardBg; diff --git a/src/routes/files.ts b/src/routes/files.ts index fe7fd3a9801197d49764529b679eda1c95c6efd9..bc88876ab1bef27c1932163b875f4b78e81132e4 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -1,23 +1,31 @@ +import profilePictureStorage, { + imageStorage, +} from "../middlewares/files/imageStorage"; + import { Application } from "express"; import cardImageStorage from "../middlewares/files/card/cardImageStorage"; +import createFile from "../middlewares/files/createFile"; import example from "../middlewares/example"; -import { fileFilter } from "../middlewares/files/fileFilter"; import getFieldValue from "../middlewares/utils/getFieldValue"; import getUser from "../middlewares/user/getUser"; +import { imageFilter } from "../middlewares/files/imageFilter"; import isLoggedIn from "../middlewares/auth/isLoggedIn"; import isRegistered from "../middlewares/auth/isRegistered"; import isStaffOrAdmin from "../middlewares/auth/isStaffOrAdmin"; import multer from "multer"; -import profilePictureStorage from "../middlewares/files/profile/profilePictureStorage"; +import noContentResponse from "../middlewares/utils/noContentResponse"; +import sendFile from "../middlewares/files/sendFile"; +import setOwnUserId from "../middlewares/user/setOwnUserId"; +import updateProfilePicture from "../middlewares/files/updateProfilePicture"; const profilePictureUpload = multer({ - storage: profilePictureStorage, - fileFilter, + storage: imageStorage("uploads/profile_pictures/"), + fileFilter: imageFilter, }); const CardImageUpload = multer({ - storage: cardImageStorage, - fileFilter, + storage: imageStorage("uploads/card_image/"), + fileFilter: imageFilter, }); const filesRoute = (prefix: string, app: Application): void => { @@ -28,22 +36,61 @@ const filesRoute = (prefix: string, app: Application): void => { isStaffOrAdmin(), getUser(), getFieldValue("profile", "picture"), - example() + sendFile() ); // Get a users accepted picture - app.get(`${prefix}/user/:userId/picture/accepted`, example()); + app.get( + `${prefix}/user/:userId/picture/accepted`, + isRegistered(), + isStaffOrAdmin(), + getUser(), + getFieldValue("profile", "acceptedPicture"), + sendFile() + ); // Get own picture - app.get(`${prefix}/me/picture`, example()); + app.get( + `${prefix}/me/picture`, + isRegistered(), + setOwnUserId(), + getUser(), + getFieldValue("profile", "picture"), + sendFile() + ); // Get own accepted picture - app.get(`${prefix}/me/picture/accepted`, example()); + app.get( + `${prefix}/me/picture/accepted`, + isRegistered(), + setOwnUserId(), + getUser(), + getFieldValue("profile", "acceptedPicture"), + sendFile() + ); // Get a users card in a Term app.get(`${prefix}/user/:userId/card/term/:termId`, example()); // Get own card in a Term app.get(`${prefix}/me/card/term/:termId`, example()); // Add a profile picture to a user - app.post(`${prefix}/user/:userId/picture`, example()); + app.post( + `${prefix}/user/:userId/picture`, + isRegistered(), + isStaffOrAdmin(), + getUser(), + profilePictureUpload.single("profile_picture"), + createFile(), + updateProfilePicture(), + noContentResponse() + ); // Add own profile picture - app.post(`${prefix}/me/picture`, example()); + app.post( + `${prefix}/me/picture`, + isRegistered(), + setOwnUserId(), + getUser(), + profilePictureUpload.single("profile_picture"), + createFile(), + updateProfilePicture(), + noContentResponse() + ); // Get a Term's card background image app.get(`${prefix}/card/bg/term/:termId`, example()); // Add a Term's card background image or update it