From e710e1a6fe0cab0ca2a2742f799633b1f3ac06e4 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Mon, 30 Sep 2024 21:08:52 -0400 Subject: [PATCH] experimental (unfinished) --- server/__tests__/image.test.ts | 81 ++++---------- server/__tests__/image.test.ts.old | 64 +++++++++++ server/config/db.ts | 2 +- server/constants/errorCodes.ts | 137 ++++++++++------------- server/controllers/quiz.ts | 143 +++++++++++------------- server/docs/addFolder-RDCU.puml | 23 ++++ server/docs/addFolder-serviceLayer.puml | 38 +++++++ server/docs/mdd.puml | 34 ++++++ server/middleware/AppError.ts | 14 ++- server/middleware/jwtToken.ts | 32 +++--- server/models/image.ts | 42 +------ server/repositories/folderRepository.ts | 139 +++++++++++++++++++++++ server/services/imageService.ts | 42 +++++++ server/services/quizService.ts | 22 ++++ 14 files changed, 536 insertions(+), 277 deletions(-) create mode 100644 server/__tests__/image.test.ts.old create mode 100644 server/docs/addFolder-RDCU.puml create mode 100644 server/docs/addFolder-serviceLayer.puml create mode 100644 server/docs/mdd.puml create mode 100644 server/repositories/folderRepository.ts create mode 100644 server/services/imageService.ts create mode 100644 server/services/quizService.ts diff --git a/server/__tests__/image.test.ts b/server/__tests__/image.test.ts index e2ed81c..9a8a98f 100644 --- a/server/__tests__/image.test.ts +++ b/server/__tests__/image.test.ts @@ -1,64 +1,25 @@ -const request = require('supertest'); -const app = require('../app.js'); -// const app = require('../routers/images.js'); -const { response } = require('express'); +import { Image } from '../models/image'; +import { User } from '../models/user'; -const BASE_URL = '/image' +describe('Image', () => { + let mockUser: User; -describe("POST /upload", () => { + beforeEach(() => { + mockUser = new User({ + email: 'test@example.com', + hashedPassword: 'hashedPassword123' + }); + }); - describe("when the jwt is not sent", () => { + it('should create an image with the correct properties', () => { + const fileName = 'test.png'; + const fileContent = Buffer.from('test content'); + const mimeType = 'image/png'; + const image = new Image(fileName, fileContent, mimeType, mockUser); - test('should respond with 401 status code', async () => { - const response = await request(app).post(BASE_URL + "/upload").send() - expect(response.statusCode).toBe(401) - }) - // respond message Accès refusé. Aucun jeton fourni. - - }) - - describe("when sent bad jwt", () => { - // respond with 401 - // respond message Accès refusé. Jeton invalide. - - }) - - describe("when sent no variables", () => { - // respond message Paramètre requis manquant. - // respond code 400 - - }) - - describe("when sent not an image file", () => { - // respond code 505 - }) - - describe("when sent image file", () => { - // respond code 200 - // json content type - // test("should reply with content type json", async () => { - // const response = await request(app).post(BASE_URL+'/upload').send() - // expect(response.headers['content-type']).toEqual(expect.stringContaining('json')) - // }) - }) - -}) - -describe("GET /get", () => { - - describe("when not give id", () => { - - }) - - describe("when not good id", () => { - - }) - - describe("when good id", () => { - // respond code 200 - // image content type - // response has something - - }) - -}) \ No newline at end of file + expect(image.file_name).toBe(fileName); + expect(image.file_content).toBe(fileContent); + expect(image.mime_type).toBe(mimeType); + expect(image.owner).toBe(mockUser); + }); +}); diff --git a/server/__tests__/image.test.ts.old b/server/__tests__/image.test.ts.old new file mode 100644 index 0000000..e2ed81c --- /dev/null +++ b/server/__tests__/image.test.ts.old @@ -0,0 +1,64 @@ +const request = require('supertest'); +const app = require('../app.js'); +// const app = require('../routers/images.js'); +const { response } = require('express'); + +const BASE_URL = '/image' + +describe("POST /upload", () => { + + describe("when the jwt is not sent", () => { + + test('should respond with 401 status code', async () => { + const response = await request(app).post(BASE_URL + "/upload").send() + expect(response.statusCode).toBe(401) + }) + // respond message Accès refusé. Aucun jeton fourni. + + }) + + describe("when sent bad jwt", () => { + // respond with 401 + // respond message Accès refusé. Jeton invalide. + + }) + + describe("when sent no variables", () => { + // respond message Paramètre requis manquant. + // respond code 400 + + }) + + describe("when sent not an image file", () => { + // respond code 505 + }) + + describe("when sent image file", () => { + // respond code 200 + // json content type + // test("should reply with content type json", async () => { + // const response = await request(app).post(BASE_URL+'/upload').send() + // expect(response.headers['content-type']).toEqual(expect.stringContaining('json')) + // }) + }) + +}) + +describe("GET /get", () => { + + describe("when not give id", () => { + + }) + + describe("when not good id", () => { + + }) + + describe("when good id", () => { + // respond code 200 + // image content type + // response has something + + }) + +}) \ No newline at end of file diff --git a/server/config/db.ts b/server/config/db.ts index 8511960..abc6063 100644 --- a/server/config/db.ts +++ b/server/config/db.ts @@ -27,4 +27,4 @@ class DBConnection { } } -export default DBConnection; +export default new DBConnection(); diff --git a/server/constants/errorCodes.ts b/server/constants/errorCodes.ts index d7ca180..7b90d78 100644 --- a/server/constants/errorCodes.ts +++ b/server/constants/errorCodes.ts @@ -1,133 +1,110 @@ -exports.UNAUTHORIZED_NO_TOKEN_GIVEN = { +export const UNAUTHORIZED_NO_TOKEN_GIVEN = { message: 'Accès refusé. Aucun jeton fourni.', code: 401 -} -exports.UNAUTHORIZED_INVALID_TOKEN = { +}; +export const UNAUTHORIZED_INVALID_TOKEN = { message: 'Accès refusé. Jeton invalide.', code: 401 -} +}; -exports.MISSING_REQUIRED_PARAMETER = { +export const MISSING_REQUIRED_PARAMETER = { message: 'Paramètre requis manquant.', code: 400 -} +}; -exports.USER_ALREADY_EXISTS = { +export const USER_ALREADY_EXISTS = { message: 'L\'utilisateur existe déjà.', code: 400 -} -exports.LOGIN_CREDENTIALS_ERROR = { +}; +export const LOGIN_CREDENTIALS_ERROR = { message: 'L\'email et le mot de passe ne correspondent pas.', code: 400 -} -exports.GENERATE_PASSWORD_ERROR = { +}; +export const GENERATE_PASSWORD_ERROR = { message: 'Une erreur s\'est produite lors de la création d\'un nouveau mot de passe.', code: 400 -} -exports.UPDATE_PASSWORD_ERROR = { +}; +export const UPDATE_PASSWORD_ERROR = { message: 'Une erreur s\'est produite lors de la mise à jours du mot de passe.', code: 400 -} -exports.DELETE_USER_ERROR = { - message: 'Une erreur s\'est produite lors de supression de l\'utilisateur.', +}; +export const DELETE_USER_ERROR = { + message: 'Une erreur s\'est produite lors de suppression de l\'utilisateur.', code: 400 -} +}; -exports.IMAGE_NOT_FOUND = { +export const IMAGE_NOT_FOUND = { message: 'Nous n\'avons pas trouvé l\'image.', code: 404 -} +}; -exports.QUIZ_NOT_FOUND = { +export const QUIZ_NOT_FOUND = { message: 'Aucun quiz portant cet identifiant n\'a été trouvé.', code: 404 -} -exports.QUIZ_ALREADY_EXISTS = { - message: 'Le quiz existe déja.', +}; +export const QUIZ_ALREADY_EXISTS = { + message: 'Le quiz existe déjà.', code: 400 -} -exports.UPDATE_QUIZ_ERROR = { +}; +export const UPDATE_QUIZ_ERROR = { message: 'Une erreur s\'est produite lors de la mise à jours du quiz.', code: 400 -} -exports.DELETE_QUIZ_ERROR = { - message: 'Une erreur s\'est produite lors de la supression du quiz.', +}; +export const DELETE_QUIZ_ERROR = { + message: 'Une erreur s\'est produite lors de la suppression du quiz.', code: 400 -} -exports.GETTING_QUIZ_ERROR = { +}; +export const GETTING_QUIZ_ERROR = { message: 'Une erreur s\'est produite lors de la récupération du quiz.', code: 400 -} -exports.MOVING_QUIZ_ERROR = { +}; +export const MOVING_QUIZ_ERROR = { message: 'Une erreur s\'est produite lors du déplacement du quiz.', code: 400 -} -exports.DUPLICATE_QUIZ_ERROR = { +}; +export const DUPLICATE_QUIZ_ERROR = { message: 'Une erreur s\'est produite lors de la duplication du quiz.', code: 400 -} -exports.COPY_QUIZ_ERROR = { +}; +export const COPY_QUIZ_ERROR = { message: 'Une erreur s\'est produite lors de la copie du quiz.', code: 400 -} +}; -exports.FOLDER_NOT_FOUND = { +export const FOLDER_NOT_FOUND = { message: 'Aucun dossier portant cet identifiant n\'a été trouvé.', code: 404 -} -exports.FOLDER_ALREADY_EXISTS = { - message: 'Le dossier existe déja.', +}; +export const FOLDER_ALREADY_EXISTS = { + message: 'Le dossier existe déjà.', code: 400 -} -exports.UPDATE_FOLDER_ERROR = { +}; +export const UPDATE_FOLDER_ERROR = { message: 'Une erreur s\'est produite lors de la mise à jours du dossier.', code: 400 -} -exports.DELETE_FOLDER_ERROR = { - message: 'Une erreur s\'est produite lors de la supression du dossier.', +}; +export const DELETE_FOLDER_ERROR = { + message: 'Une erreur s\'est produite lors de la suppression du dossier.', code: 400 -} -exports.GETTING_FOLDER_ERROR = { +}; +export const GETTING_FOLDER_ERROR = { message: 'Une erreur s\'est produite lors de la récupération du dossier.', code: 400 -} -exports.MOVING_FOLDER_ERROR = { +}; +export const MOVING_FOLDER_ERROR = { message: 'Une erreur s\'est produite lors du déplacement du dossier.', code: 400 -} -exports.DUPLICATE_FOLDER_ERROR = { +}; +export const DUPLICATE_FOLDER_ERROR = { message: 'Une erreur s\'est produite lors de la duplication du dossier.', code: 400 -} -exports.COPY_FOLDER_ERROR = { +}; +export const COPY_FOLDER_ERROR = { message: 'Une erreur s\'est produite lors de la copie du dossier.', code: 400 -} +}; - - - - - - - - - - - - - - - - -exports.NOT_IMPLEMENTED = { +export const NOT_IMPLEMENTED = { message: 'Route not implemented yet!', code: 400 -} - - -// static ok(res, results) {200 -// static badRequest(res, message) {400 -// static unauthorized(res, message) {401 -// static notFound(res, message) {404 -// static serverError(res, message) {505 \ No newline at end of file +}; diff --git a/server/controllers/quiz.ts b/server/controllers/quiz.ts index 8b8b247..d446014 100644 --- a/server/controllers/quiz.ts +++ b/server/controllers/quiz.ts @@ -1,13 +1,22 @@ -const model = require('../models/quiz.js'); -const folderModel = require('../models/folders.js'); -const emailer = require('../config/email.js'); - -const AppError = require('../middleware/AppError.js'); -const { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, QUIZ_NOT_FOUND, FOLDER_NOT_FOUND, QUIZ_ALREADY_EXISTS, GETTING_QUIZ_ERROR, DELETE_QUIZ_ERROR, UPDATE_QUIZ_ERROR, MOVING_QUIZ_ERROR, DUPLICATE_QUIZ_ERROR, COPY_QUIZ_ERROR } = require('../constants/errorCodes'); +import { Request, Response, NextFunction } from 'express'; +import AppError from '../middleware/AppError'; +import { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, QUIZ_NOT_FOUND, FOLDER_NOT_FOUND, QUIZ_ALREADY_EXISTS, GETTING_QUIZ_ERROR, DELETE_QUIZ_ERROR, UPDATE_QUIZ_ERROR, MOVING_QUIZ_ERROR } from '../constants/errorCodes'; +import { Quiz } from '../models/quiz'; +import { Folder } from '../models/folder'; +import Emailer from '../config/email'; class QuizController { + private quiz: Quiz; + private folder: Folder; + private emailer: Emailer; - async create(req, res, next) { + constructor() { + this.folder = new Folder(); + this.quiz = new Quiz(); + this.emailer = new Emailer(); + } + + async create(req: Request, res: Response, next: NextFunction) { try { const { title, content, folderId } = req.body; @@ -16,13 +25,13 @@ class QuizController { } // Is this folder mine - const owner = await folderModel.getOwner(folderId); + const owner = await this.folder.getOwner(folderId); if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - const result = await model.create(title, content, folderId, req.user.userId); + const result = await this.quiz.create(title, content, folderId, req.user.userId); if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); @@ -38,7 +47,7 @@ class QuizController { } } - async get(req, res, next) { + async get(req: Request, res: Response, next: NextFunction) { try { const { quizId } = req.params; @@ -47,7 +56,7 @@ class QuizController { } - const content = await model.getContent(quizId); + const content = await this.quiz.getContent(quizId); if (!content) { throw new AppError(GETTING_QUIZ_ERROR); @@ -68,7 +77,7 @@ class QuizController { } } - async delete(req, res, next) { + async delete(req: Request, res: Response, next: NextFunction) { try { const { quizId } = req.params; @@ -77,13 +86,13 @@ class QuizController { } // Is this quiz mine - const owner = await model.getOwner(quizId); + const owner = await this.quiz.getOwner(quizId); if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - const result = await model.delete(quizId); + const result = await this.quiz.delete(quizId); if (!result) { throw new AppError(DELETE_QUIZ_ERROR); @@ -99,7 +108,7 @@ class QuizController { } } - async update(req, res, next) { + async update(req: Request, res: Response, next: NextFunction) { try { const { quizId, newTitle, newContent } = req.body; @@ -108,13 +117,13 @@ class QuizController { } // Is this quiz mine - const owner = await model.getOwner(quizId); + const owner = await this.quiz.getOwner(quizId); if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - const result = await model.update(quizId, newTitle, newContent); + const result = await this.quiz.update(quizId, newTitle, newContent); if (!result) { throw new AppError(UPDATE_QUIZ_ERROR); @@ -130,7 +139,7 @@ class QuizController { } } - async move(req, res, next) { + async move(req: Request, res: Response, next: NextFunction) { try { const { quizId, newFolderId } = req.body; @@ -139,20 +148,20 @@ class QuizController { } // Is this quiz mine - const quizOwner = await model.getOwner(quizId); + const quizOwner = await this.quiz.getOwner(quizId); if (quizOwner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } // Is this folder mine - const folderOwner = await folderModel.getOwner(newFolderId); + const folderOwner = await this.folder.getOwner(newFolderId); if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - const result = await model.move(quizId, newFolderId); + const result = await this.quiz.move(quizId, newFolderId); if (!result) { throw new AppError(MOVING_QUIZ_ERROR); @@ -169,8 +178,7 @@ class QuizController { } - - async copy(req, res, next) { + async copy(req: Request, res: Response, next: NextFunction) { const { quizId, newTitle, folderId } = req.body; if (!quizId || !newTitle || !folderId) { @@ -178,32 +186,9 @@ class QuizController { } throw new AppError(NOT_IMPLEMENTED); - // const { quizId } = req.params; - // const { newUserId } = req.body; - - // try { - // //Trouver le quiz a dupliquer - // const conn = db.getConnection(); - // const quiztoduplicate = await conn.collection('quiz').findOne({ _id: new ObjectId(quizId) }); - // if (!quiztoduplicate) { - // throw new Error("Quiz non trouvé"); - // } - // console.log(quiztoduplicate); - // //Suppression du id du quiz pour ne pas le répliquer - // delete quiztoduplicate._id; - // //Ajout du duplicata - // await conn.collection('quiz').insertOne({ ...quiztoduplicate, userId: new ObjectId(newUserId) }); - // res.json(Response.ok("Dossier dupliqué avec succès pour un autre utilisateur")); - - // } catch (error) { - // if (error.message.startsWith("Quiz non trouvé")) { - // return res.status(404).json(Response.badRequest(error.message)); - // } - // res.status(500).json(Response.serverError(error.message)); - // } } - async deleteQuizzesByFolderId(req, res, next) { + async deleteQuizzesByFolderId(req: Request, res: Response, next: NextFunction) { try { const { folderId } = req.body; @@ -212,7 +197,7 @@ class QuizController { } // Call the method from the Quiz model to delete quizzes by folder ID - await Quiz.deleteQuizzesByFolderId(folderId); + await this.quiz.deleteQuizzesByFolderId(folderId); return res.status(200).json({ message: 'Quizzes deleted successfully.' @@ -222,57 +207,57 @@ class QuizController { } } - async duplicate(req, res, next) { - const { quizId } = req.body; + async duplicate(req: Request, res: Response, next: NextFunction) { + const { quizId } = req.body; try { - const newQuizId = await model.duplicate(quizId,req.user.userId); + const newQuizId = await this.quiz.duplicate(quizId, req.user.userId); res.status(200).json({ success: true, newQuizId }); } catch (error) { return next(error); } } - async quizExists(title, userId) { + async quizExists(title: string, userId: string) { try { - const existingFile = await model.quizExists(title, userId); + const existingFile = await this.quiz.quizExists(title, userId); return existingFile !== null; } catch (error) { throw new AppError(GETTING_QUIZ_ERROR); } } - async Share(req, res, next) { + async Share(req: Request, res: Response, next: NextFunction) { try { const { quizId, email } = req.body; - - if ( !quizId || !email) { + + if (!quizId || !email) { throw new AppError(MISSING_REQUIRED_PARAMETER); - } + } const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`; - emailer.quizShare(email, link); - + this.emailer.quizShare(email, link); + return res.status(200).json({ message: 'Quiz partagé avec succès.' }); - + } catch (error) { return next(error); } - } - - async getShare(req, res, next) { + } + + async getShare(req: Request, res: Response, next: NextFunction) { try { const { quizId } = req.params; - - if ( !quizId ) { - throw new AppError(MISSING_REQUIRED_PARAMETER); - } - const content = await model.getContent(quizId); + if (!quizId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const content = await this.quiz.getContent(quizId); if (!content) { throw new AppError(GETTING_QUIZ_ERROR); @@ -281,36 +266,36 @@ class QuizController { return res.status(200).json({ data: content.title }); - + } catch (error) { return next(error); } } - async receiveShare(req, res, next) { + async receiveShare(req: Request, res: Response, next: NextFunction) { try { const { quizId, folderId } = req.body; - + if (!quizId || !folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - const folderOwner = await folderModel.getOwner(folderId); + const folderOwner = await this.folder.getOwner(folderId); if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const content = await model.getContent(quizId); + + const content = await this.quiz.getContent(quizId); if (!content) { throw new AppError(GETTING_QUIZ_ERROR); } - - const result = await model.create(content.title, content.content, folderId, req.user.userId); + + const result = await this.quiz.create(content.title, content.content, folderId, req.user.userId); if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); } - + return res.status(200).json({ message: 'Quiz partagé reçu.' }); @@ -319,8 +304,6 @@ class QuizController { return next(error); } } - - } -module.exports = new QuizController; \ No newline at end of file +export default new QuizController(); diff --git a/server/docs/addFolder-RDCU.puml b/server/docs/addFolder-RDCU.puml new file mode 100644 index 0000000..5fcf697 --- /dev/null +++ b/server/docs/addFolder-RDCU.puml @@ -0,0 +1,23 @@ +@startuml addFolder-RDCU +skinparam style strictuml +skinparam SequenceMessageAlignment center + +participant ":Folder\nController" as FC +participant ":Évalue\nTon\nSavoir" as ETS +participant ":Map" as MUS +participant "user:User" as U +participant "f:Folder" as F + +-> FC: addFolder(folderName: string,\nuserId: string) +FC -> ETS : user = getUser(userId: string) +note right: by Expert +ETS -> MUS : user = find(userId) +note right: id to object +FC -> ETS : f = createFolder(folderName: string,\nuser: User) +create F +ETS -->> F : <> +note right: by Creator (ETS\naggregates Folder) +ETS -> U : addFolder(f) +note right: by Expert + +@enduml diff --git a/server/docs/addFolder-serviceLayer.puml b/server/docs/addFolder-serviceLayer.puml new file mode 100644 index 0000000..857f232 --- /dev/null +++ b/server/docs/addFolder-serviceLayer.puml @@ -0,0 +1,38 @@ +@startuml +skinparam style strictuml +participant ":Folder\nController" as FC +participant ":Évalue\nTon\nSavoir" as ETS +participant ":Map" as MUS +participant "user:User" as U +participant "f:Folder" as F +participant "FolderService" as FS +participant "UserRepository" as UR +participant "FolderRepository" as FR +database "Database" as DB + +-> FC: addFolder(folderName: string,\nuserId: string) +FC -> ETS : user = getUser(userId: string) +note right: by Expert +ETS -> MUS : user = find(userId) +note right: id to object +FC -> ETS : f = createFolder(folderName: string,\nuser: User) +create F +ETS -->> F : <> +note right: by Creator (ETS\naggregates Folder) +ETS -> U : addFolder(f) +note right: by Expert + +ETS -> FS: addFolderToUser(user, f) +FS -> FR: save(f) +FR -> DB: insertOne(f) +DB --> FR: insertedId +FR --> FS: save result + +FS -> UR: update(user) +UR -> DB: updateOne({ _id: user._id }, { $set: { folders: user.folders } }) +DB --> UR: update result +UR --> FS: update result + +FS --> ETS: success +ETS --> FC: success +@enduml diff --git a/server/docs/mdd.puml b/server/docs/mdd.puml new file mode 100644 index 0000000..a8ccd36 --- /dev/null +++ b/server/docs/mdd.puml @@ -0,0 +1,34 @@ +@startuml MDD EvalueTonSavoir +skinparam style strictuml +hide empty members + +class "EvalueTonSavoir" as ETS + +class User { + id: string + email: string + hashedPassword: string + createdAt: Date +} + +class Quiz { + id: string + title: string + content: string + createdAt: Date + updatedAt: Date +} + +class Folder { + id: string + title: string + createdAt: Date +} + +User "1" -- "1..*" Folder : Creates > +Folder "1" *-- "1..*" Quiz : Contains > +User "1" *-- "1..*" Quiz : Creates > +ETS "1" *-- "1..*" User : Contains > +ETS "1" *-- "1..*" Folder : Contains > +ETS "1" *-- "1..*" Quiz : Contains > +@enduml diff --git a/server/middleware/AppError.ts b/server/middleware/AppError.ts index 9744646..af9b525 100644 --- a/server/middleware/AppError.ts +++ b/server/middleware/AppError.ts @@ -1,8 +1,16 @@ +interface ErrorCode { + message: string; + code: number; +} + class AppError extends Error { - constructor (errorCode) { - super(errorCode.message) + statusCode: number; + + constructor(errorCode: ErrorCode) { + super(errorCode.message); this.statusCode = errorCode.code; + Object.setPrototypeOf(this, AppError.prototype); // Ensure the prototype chain is correctly set } } -module.exports = AppError; \ No newline at end of file +export default AppError; diff --git a/server/middleware/jwtToken.ts b/server/middleware/jwtToken.ts index 292e591..95469df 100644 --- a/server/middleware/jwtToken.ts +++ b/server/middleware/jwtToken.ts @@ -1,37 +1,35 @@ -const jwt = require('jsonwebtoken') -const dotenv = require('dotenv') -const AppError = require('./AppError.js'); -const { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } = require('../constants/errorCodes'); +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import AppError from './AppError'; +import { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } from '../constants/errorCodes'; +import { Request, Response, NextFunction } from 'express'; dotenv.config(); class Token { - - create(email, userId) { - return jwt.sign({ email, userId }, process.env.JWT_SECRET); + create(email: string, userId: string): string { + return jwt.sign({ email, userId }, process.env.JWT_SECRET as string, { expiresIn: '2h' }); } - authenticate(req, res, next) { + authenticate(req: Request, res: Response, next: NextFunction): void { try { - const token = req.header('Authorization') && req.header('Authorization').split(' ')[1]; + const authHeader = req.header('Authorization'); + const token = authHeader && authHeader.split(' ')[1]; if (!token) { throw new AppError(UNAUTHORIZED_NO_TOKEN_GIVEN); } - jwt.verify(token, process.env.JWT_SECRET, (error, payload) => { + jwt.verify(token, process.env.JWT_SECRET as string, (error, payload) => { if (error) { - throw new AppError(UNAUTHORIZED_INVALID_TOKEN) + throw new AppError(UNAUTHORIZED_INVALID_TOKEN); } - req.user = payload; + next(); }); - } catch (error) { - return next(error); + next(error); } - - return next(); } } -module.exports = new Token(); \ No newline at end of file +export default new Token(); diff --git a/server/models/image.ts b/server/models/image.ts index 8dc8508..aa9ec2c 100644 --- a/server/models/image.ts +++ b/server/models/image.ts @@ -1,42 +1,12 @@ -import db from '../config/db'; -import { ObjectId } from 'mongodb'; +import { User } from "./user"; export class Image { - async upload(file: Express.Multer.File, userId: string): Promise { - await db.connect(); - const conn = db.getConnection(); - const imagesCollection = conn.collection('images'); - - const newImage = { - userId: userId, - file_name: file.originalname, - file_content: file.buffer.toString('base64'), - mime_type: file.mimetype, - created_at: new Date(), - }; - - const result = await imagesCollection.insertOne(newImage); - - return result.insertedId.toString(); + constructor(public file_name: string, public file_content: Buffer, public mime_type: string, public owner: User) { + this.file_name = file_name; + this.file_content = file_content; + this.mime_type = mime_type; + this.owner = owner; } - async get(id: string): Promise<{ file_name: string; file_content: Buffer; mime_type: string } | null> { - await db.connect(); - const conn = db.getConnection(); - - const imagesCollection = conn.collection('images'); - - const result = await imagesCollection.findOne({ _id: new ObjectId(id) }); - - if (!result) return null; - - return { - file_name: result.file_name, - file_content: Buffer.from(result.file_content, 'base64'), - mime_type: result.mime_type, - }; - } } - -export default new Image(); diff --git a/server/repositories/folderRepository.ts b/server/repositories/folderRepository.ts new file mode 100644 index 0000000..f312b35 --- /dev/null +++ b/server/repositories/folderRepository.ts @@ -0,0 +1,139 @@ +import { Db, ObjectId } from 'mongodb'; +import DBConnection from '../config/db'; +import { Folder } from '../models/folder'; + +class FolderRepository { + private db: DBConnection; + + constructor() { + this.db = new DBConnection(); + } + + async createFolder(folder: Folder): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const folderCollection = conn.collection('folders'); + + const existingFolder = await folderCollection.findOne({ title: folder.title, userId: folder.userId }); + + if (existingFolder) throw new Error('Dossier existe déjà.'); + + const result = await folderCollection.insertOne(folder); + return result.insertedId; + } + + async getUserFolders(userId: string): Promise { + await db.connect(); + const conn = db.getConnection(); + + const foldersCollection = conn.collection('folders'); + + const result = await foldersCollection.find({ userId }).toArray(); + + return result; + } + + + async getOwner(folderId: string): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const folderCollection = conn.collection('folders'); + + const folder = await folderCollection.findOne({ _id: new ObjectId(folderId) }); + + if (!folder) throw new Error('Dossier non trouvé.'); + + return folder.userId; + } + + async getContent(folderId: string): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const filesCollection = conn.collection('files'); + + const result = await filesCollection.find({ folderId }).toArray(); + + return result; + } + + async delete(folderId: string): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const folderCollection = conn.collection('folders'); + + // can't delete a folder if it has quizzes in it + const filesCollection = conn.collection('files'); + const quizzes = await filesCollection.find({ folderId }).toArray(); + if (quizzes.length > 0) throw new Error('Dossier non vide.'); + + const result = await folderCollection.deleteOne({ _id: new ObjectId(folderId) }); + + return result.deletedCount === 1; + } + + // async deleteQuizzes(folderId: string): Promise { + // await this.db.connect(); + // const conn: Db = this.db.getConnection(); + + // const quizCollection = conn.collection('files'); + + // const result = await quizCollection.deleteMany({ folderId: folderId }); + // return result.deletedCount || 0; + // } + + async rename(folderId: string, newTitle: string): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const folderCollection = conn.collection('folders'); + + const result = await folderCollection.updateOne( + { _id: new ObjectId(folderId) }, + { $set: { title: newTitle } } + ); + + return result.modifiedCount === 1; + } + + async duplicate(folderId: string): Promise { + const sourceFolder = await this.getFolderWithContent(folderId); + + let newFolderTitle = `${sourceFolder.title}-copie`; + let counter = 1; + + while (await sourceFolder.folderExists(newFolderTitle, sourceFolder.userId)) { + newFolderTitle = `${sourceFolder.title}-copie(${counter})`; + counter++; + } + + const newFolderId = await this.createFolder(newFolderTitle); + + if (!newFolderId) { + throw new Error('Failed to create a duplicate folder.'); + } + + for (const quiz of sourceFolder.content) { + const { title, content } = quiz; + await Quiz.create(title, content, newFolderId.toString(), userId); + } + + return newFolderId; + } + + async quizExists(title: string, userId: string): Promise { + await this.db.connect(); + const conn: Db = this.db.getConnection(); + + const quizCollection = conn.collection('files'); + + const existingQuiz = await quizCollection.findOne({ title: title, userId: userId }); + + return existingQuiz !== null; + } +} + +export default QuizRepository; diff --git a/server/services/imageService.ts b/server/services/imageService.ts new file mode 100644 index 0000000..8dc8508 --- /dev/null +++ b/server/services/imageService.ts @@ -0,0 +1,42 @@ +import db from '../config/db'; +import { ObjectId } from 'mongodb'; + +export class Image { + async upload(file: Express.Multer.File, userId: string): Promise { + await db.connect(); + const conn = db.getConnection(); + + const imagesCollection = conn.collection('images'); + + const newImage = { + userId: userId, + file_name: file.originalname, + file_content: file.buffer.toString('base64'), + mime_type: file.mimetype, + created_at: new Date(), + }; + + const result = await imagesCollection.insertOne(newImage); + + return result.insertedId.toString(); + } + + async get(id: string): Promise<{ file_name: string; file_content: Buffer; mime_type: string } | null> { + await db.connect(); + const conn = db.getConnection(); + + const imagesCollection = conn.collection('images'); + + const result = await imagesCollection.findOne({ _id: new ObjectId(id) }); + + if (!result) return null; + + return { + file_name: result.file_name, + file_content: Buffer.from(result.file_content, 'base64'), + mime_type: result.mime_type, + }; + } +} + +export default new Image(); diff --git a/server/services/quizService.ts b/server/services/quizService.ts new file mode 100644 index 0000000..b9ae3e3 --- /dev/null +++ b/server/services/quizService.ts @@ -0,0 +1,22 @@ +import { QuizRepository } from '../repositories/quizRepository'; +import { Quiz } from '../models/quiz'; + +// define a parameter object for the createQuiz method +interface CreateQuizParams { + title: string; + content: string; + folder: Folder; + user: User; +} + +export class QuizService { + constructor(private quizRepository: QuizRepository) {} + + async createQuiz(params: CreateQuizParams): Promise { + // Create a new Quiz object without an ID + const quiz = new Quiz(params.folder, params.user, params.title, params.content); + + // Save the quiz to the database, and the repository assigns the ID + return this.quizRepository.save(quiz); + } +}