experimental (unfinished)

This commit is contained in:
C. Fuhrman 2024-09-30 21:08:52 -04:00
parent 265f9a2b76
commit e710e1a6fe
14 changed files with 536 additions and 277 deletions

View file

@ -1,64 +1,25 @@
const request = require('supertest'); import { Image } from '../models/image';
const app = require('../app.js'); import { User } from '../models/user';
// const app = require('../routers/images.js');
const { response } = require('express');
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 () => { expect(image.file_name).toBe(fileName);
const response = await request(app).post(BASE_URL + "/upload").send() expect(image.file_content).toBe(fileContent);
expect(response.statusCode).toBe(401) expect(image.mime_type).toBe(mimeType);
}) expect(image.owner).toBe(mockUser);
// 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
})
})

View file

@ -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
})
})

View file

@ -27,4 +27,4 @@ class DBConnection {
} }
} }
export default DBConnection; export default new DBConnection();

View file

@ -1,133 +1,110 @@
exports.UNAUTHORIZED_NO_TOKEN_GIVEN = { export const UNAUTHORIZED_NO_TOKEN_GIVEN = {
message: 'Accès refusé. Aucun jeton fourni.', message: 'Accès refusé. Aucun jeton fourni.',
code: 401 code: 401
} };
exports.UNAUTHORIZED_INVALID_TOKEN = { export const UNAUTHORIZED_INVALID_TOKEN = {
message: 'Accès refusé. Jeton invalide.', message: 'Accès refusé. Jeton invalide.',
code: 401 code: 401
} };
exports.MISSING_REQUIRED_PARAMETER = { export const MISSING_REQUIRED_PARAMETER = {
message: 'Paramètre requis manquant.', message: 'Paramètre requis manquant.',
code: 400 code: 400
} };
exports.USER_ALREADY_EXISTS = { export const USER_ALREADY_EXISTS = {
message: 'L\'utilisateur existe déjà.', message: 'L\'utilisateur existe déjà.',
code: 400 code: 400
} };
exports.LOGIN_CREDENTIALS_ERROR = { export const LOGIN_CREDENTIALS_ERROR = {
message: 'L\'email et le mot de passe ne correspondent pas.', message: 'L\'email et le mot de passe ne correspondent pas.',
code: 400 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.', message: 'Une erreur s\'est produite lors de la création d\'un nouveau mot de passe.',
code: 400 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.', message: 'Une erreur s\'est produite lors de la mise à jours du mot de passe.',
code: 400 code: 400
} };
exports.DELETE_USER_ERROR = { export const DELETE_USER_ERROR = {
message: 'Une erreur s\'est produite lors de supression de l\'utilisateur.', message: 'Une erreur s\'est produite lors de suppression de l\'utilisateur.',
code: 400 code: 400
} };
exports.IMAGE_NOT_FOUND = { export const IMAGE_NOT_FOUND = {
message: 'Nous n\'avons pas trouvé l\'image.', message: 'Nous n\'avons pas trouvé l\'image.',
code: 404 code: 404
} };
exports.QUIZ_NOT_FOUND = { export const QUIZ_NOT_FOUND = {
message: 'Aucun quiz portant cet identifiant n\'a été trouvé.', message: 'Aucun quiz portant cet identifiant n\'a été trouvé.',
code: 404 code: 404
} };
exports.QUIZ_ALREADY_EXISTS = { export const QUIZ_ALREADY_EXISTS = {
message: 'Le quiz existe déja.', message: 'Le quiz existe déjà.',
code: 400 code: 400
} };
exports.UPDATE_QUIZ_ERROR = { export const UPDATE_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la mise à jours du quiz.', message: 'Une erreur s\'est produite lors de la mise à jours du quiz.',
code: 400 code: 400
} };
exports.DELETE_QUIZ_ERROR = { export const DELETE_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la supression du quiz.', message: 'Une erreur s\'est produite lors de la suppression du quiz.',
code: 400 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.', message: 'Une erreur s\'est produite lors de la récupération du quiz.',
code: 400 code: 400
} };
exports.MOVING_QUIZ_ERROR = { export const MOVING_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors du déplacement du quiz.', message: 'Une erreur s\'est produite lors du déplacement du quiz.',
code: 400 code: 400
} };
exports.DUPLICATE_QUIZ_ERROR = { export const DUPLICATE_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la duplication du quiz.', message: 'Une erreur s\'est produite lors de la duplication du quiz.',
code: 400 code: 400
} };
exports.COPY_QUIZ_ERROR = { export const COPY_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la copie du quiz.', message: 'Une erreur s\'est produite lors de la copie du quiz.',
code: 400 code: 400
} };
exports.FOLDER_NOT_FOUND = { export const FOLDER_NOT_FOUND = {
message: 'Aucun dossier portant cet identifiant n\'a été trouvé.', message: 'Aucun dossier portant cet identifiant n\'a été trouvé.',
code: 404 code: 404
} };
exports.FOLDER_ALREADY_EXISTS = { export const FOLDER_ALREADY_EXISTS = {
message: 'Le dossier existe déja.', message: 'Le dossier existe déjà.',
code: 400 code: 400
} };
exports.UPDATE_FOLDER_ERROR = { export const UPDATE_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la mise à jours du dossier.', message: 'Une erreur s\'est produite lors de la mise à jours du dossier.',
code: 400 code: 400
} };
exports.DELETE_FOLDER_ERROR = { export const DELETE_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la supression du dossier.', message: 'Une erreur s\'est produite lors de la suppression du dossier.',
code: 400 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.', message: 'Une erreur s\'est produite lors de la récupération du dossier.',
code: 400 code: 400
} };
exports.MOVING_FOLDER_ERROR = { export const MOVING_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors du déplacement du dossier.', message: 'Une erreur s\'est produite lors du déplacement du dossier.',
code: 400 code: 400
} };
exports.DUPLICATE_FOLDER_ERROR = { export const DUPLICATE_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la duplication du dossier.', message: 'Une erreur s\'est produite lors de la duplication du dossier.',
code: 400 code: 400
} };
exports.COPY_FOLDER_ERROR = { export const COPY_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la copie du dossier.', message: 'Une erreur s\'est produite lors de la copie du dossier.',
code: 400 code: 400
} };
export const NOT_IMPLEMENTED = {
exports.NOT_IMPLEMENTED = {
message: 'Route not implemented yet!', message: 'Route not implemented yet!',
code: 400 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

View file

@ -1,13 +1,22 @@
const model = require('../models/quiz.js'); import { Request, Response, NextFunction } from 'express';
const folderModel = require('../models/folders.js'); import AppError from '../middleware/AppError';
const emailer = require('../config/email.js'); 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';
const AppError = require('../middleware/AppError.js'); import { Folder } from '../models/folder';
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 Emailer from '../config/email';
class QuizController { 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 { try {
const { title, content, folderId } = req.body; const { title, content, folderId } = req.body;
@ -16,13 +25,13 @@ class QuizController {
} }
// Is this folder mine // Is this folder mine
const owner = await folderModel.getOwner(folderId); const owner = await this.folder.getOwner(folderId);
if (owner != req.user.userId) { if (owner != req.user.userId) {
throw new AppError(FOLDER_NOT_FOUND); 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) { if (!result) {
throw new AppError(QUIZ_ALREADY_EXISTS); 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 { try {
const { quizId } = req.params; 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) { if (!content) {
throw new AppError(GETTING_QUIZ_ERROR); 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 { try {
const { quizId } = req.params; const { quizId } = req.params;
@ -77,13 +86,13 @@ class QuizController {
} }
// Is this quiz mine // Is this quiz mine
const owner = await model.getOwner(quizId); const owner = await this.quiz.getOwner(quizId);
if (owner != req.user.userId) { if (owner != req.user.userId) {
throw new AppError(QUIZ_NOT_FOUND); throw new AppError(QUIZ_NOT_FOUND);
} }
const result = await model.delete(quizId); const result = await this.quiz.delete(quizId);
if (!result) { if (!result) {
throw new AppError(DELETE_QUIZ_ERROR); 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 { try {
const { quizId, newTitle, newContent } = req.body; const { quizId, newTitle, newContent } = req.body;
@ -108,13 +117,13 @@ class QuizController {
} }
// Is this quiz mine // Is this quiz mine
const owner = await model.getOwner(quizId); const owner = await this.quiz.getOwner(quizId);
if (owner != req.user.userId) { if (owner != req.user.userId) {
throw new AppError(QUIZ_NOT_FOUND); 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) { if (!result) {
throw new AppError(UPDATE_QUIZ_ERROR); 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 { try {
const { quizId, newFolderId } = req.body; const { quizId, newFolderId } = req.body;
@ -139,20 +148,20 @@ class QuizController {
} }
// Is this quiz mine // Is this quiz mine
const quizOwner = await model.getOwner(quizId); const quizOwner = await this.quiz.getOwner(quizId);
if (quizOwner != req.user.userId) { if (quizOwner != req.user.userId) {
throw new AppError(QUIZ_NOT_FOUND); throw new AppError(QUIZ_NOT_FOUND);
} }
// Is this folder mine // Is this folder mine
const folderOwner = await folderModel.getOwner(newFolderId); const folderOwner = await this.folder.getOwner(newFolderId);
if (folderOwner != req.user.userId) { if (folderOwner != req.user.userId) {
throw new AppError(FOLDER_NOT_FOUND); throw new AppError(FOLDER_NOT_FOUND);
} }
const result = await model.move(quizId, newFolderId); const result = await this.quiz.move(quizId, newFolderId);
if (!result) { if (!result) {
throw new AppError(MOVING_QUIZ_ERROR); throw new AppError(MOVING_QUIZ_ERROR);
@ -169,8 +178,7 @@ class QuizController {
} }
async copy(req: Request, res: Response, next: NextFunction) {
async copy(req, res, next) {
const { quizId, newTitle, folderId } = req.body; const { quizId, newTitle, folderId } = req.body;
if (!quizId || !newTitle || !folderId) { if (!quizId || !newTitle || !folderId) {
@ -178,32 +186,9 @@ class QuizController {
} }
throw new AppError(NOT_IMPLEMENTED); 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 { try {
const { folderId } = req.body; const { folderId } = req.body;
@ -212,7 +197,7 @@ class QuizController {
} }
// Call the method from the Quiz model to delete quizzes by folder ID // 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({ return res.status(200).json({
message: 'Quizzes deleted successfully.' message: 'Quizzes deleted successfully.'
@ -222,27 +207,27 @@ class QuizController {
} }
} }
async duplicate(req, res, next) { async duplicate(req: Request, res: Response, next: NextFunction) {
const { quizId } = req.body; const { quizId } = req.body;
try { 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 }); res.status(200).json({ success: true, newQuizId });
} catch (error) { } catch (error) {
return next(error); return next(error);
} }
} }
async quizExists(title, userId) { async quizExists(title: string, userId: string) {
try { try {
const existingFile = await model.quizExists(title, userId); const existingFile = await this.quiz.quizExists(title, userId);
return existingFile !== null; return existingFile !== null;
} catch (error) { } catch (error) {
throw new AppError(GETTING_QUIZ_ERROR); throw new AppError(GETTING_QUIZ_ERROR);
} }
} }
async Share(req, res, next) { async Share(req: Request, res: Response, next: NextFunction) {
try { try {
const { quizId, email } = req.body; const { quizId, email } = req.body;
@ -252,7 +237,7 @@ class QuizController {
const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`; const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`;
emailer.quizShare(email, link); this.emailer.quizShare(email, link);
return res.status(200).json({ return res.status(200).json({
message: 'Quiz partagé avec succès.' message: 'Quiz partagé avec succès.'
@ -264,7 +249,7 @@ class QuizController {
} }
} }
async getShare(req, res, next) { async getShare(req: Request, res: Response, next: NextFunction) {
try { try {
const { quizId } = req.params; const { quizId } = req.params;
@ -272,7 +257,7 @@ class QuizController {
throw new AppError(MISSING_REQUIRED_PARAMETER); throw new AppError(MISSING_REQUIRED_PARAMETER);
} }
const content = await model.getContent(quizId); const content = await this.quiz.getContent(quizId);
if (!content) { if (!content) {
throw new AppError(GETTING_QUIZ_ERROR); throw new AppError(GETTING_QUIZ_ERROR);
@ -288,7 +273,7 @@ class QuizController {
} }
} }
async receiveShare(req, res, next) { async receiveShare(req: Request, res: Response, next: NextFunction) {
try { try {
const { quizId, folderId } = req.body; const { quizId, folderId } = req.body;
@ -296,17 +281,17 @@ class QuizController {
throw new AppError(MISSING_REQUIRED_PARAMETER); throw new AppError(MISSING_REQUIRED_PARAMETER);
} }
const folderOwner = await folderModel.getOwner(folderId); const folderOwner = await this.folder.getOwner(folderId);
if (folderOwner != req.user.userId) { if (folderOwner != req.user.userId) {
throw new AppError(FOLDER_NOT_FOUND); throw new AppError(FOLDER_NOT_FOUND);
} }
const content = await model.getContent(quizId); const content = await this.quiz.getContent(quizId);
if (!content) { if (!content) {
throw new AppError(GETTING_QUIZ_ERROR); 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) { if (!result) {
throw new AppError(QUIZ_ALREADY_EXISTS); throw new AppError(QUIZ_ALREADY_EXISTS);
} }
@ -319,8 +304,6 @@ class QuizController {
return next(error); return next(error);
} }
} }
} }
module.exports = new QuizController; export default new QuizController();

View file

@ -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<string, User>" 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 : <<create>>
note right: by Creator (ETS\naggregates Folder)
ETS -> U : addFolder(f)
note right: by Expert
@enduml

View file

@ -0,0 +1,38 @@
@startuml
skinparam style strictuml
participant ":Folder\nController" as FC
participant ":Évalue\nTon\nSavoir" as ETS
participant ":Map<string, User>" 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 : <<create>>
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

34
server/docs/mdd.puml Normal file
View file

@ -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

View file

@ -1,8 +1,16 @@
interface ErrorCode {
message: string;
code: number;
}
class AppError extends Error { class AppError extends Error {
constructor (errorCode) { statusCode: number;
super(errorCode.message)
constructor(errorCode: ErrorCode) {
super(errorCode.message);
this.statusCode = errorCode.code; this.statusCode = errorCode.code;
Object.setPrototypeOf(this, AppError.prototype); // Ensure the prototype chain is correctly set
} }
} }
module.exports = AppError; export default AppError;

View file

@ -1,37 +1,35 @@
const jwt = require('jsonwebtoken') import jwt from 'jsonwebtoken';
const dotenv = require('dotenv') import dotenv from 'dotenv';
const AppError = require('./AppError.js'); import AppError from './AppError';
const { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } = require('../constants/errorCodes'); import { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } from '../constants/errorCodes';
import { Request, Response, NextFunction } from 'express';
dotenv.config(); dotenv.config();
class Token { class Token {
create(email: string, userId: string): string {
create(email, userId) { return jwt.sign({ email, userId }, process.env.JWT_SECRET as string, { expiresIn: '2h' });
return jwt.sign({ email, userId }, process.env.JWT_SECRET);
} }
authenticate(req, res, next) { authenticate(req: Request, res: Response, next: NextFunction): void {
try { try {
const token = req.header('Authorization') && req.header('Authorization').split(' ')[1]; const authHeader = req.header('Authorization');
const token = authHeader && authHeader.split(' ')[1];
if (!token) { if (!token) {
throw new AppError(UNAUTHORIZED_NO_TOKEN_GIVEN); 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) { if (error) {
throw new AppError(UNAUTHORIZED_INVALID_TOKEN) throw new AppError(UNAUTHORIZED_INVALID_TOKEN);
} }
req.user = payload; req.user = payload;
next();
}); });
} catch (error) { } catch (error) {
return next(error); next(error);
} }
return next();
} }
} }
module.exports = new Token(); export default new Token();

View file

@ -1,42 +1,12 @@
import db from '../config/db'; import { User } from "./user";
import { ObjectId } from 'mongodb';
export class Image { export class Image {
async upload(file: Express.Multer.File, userId: string): Promise<string> {
await db.connect();
const conn = db.getConnection();
const imagesCollection = conn.collection('images'); constructor(public file_name: string, public file_content: Buffer, public mime_type: string, public owner: User) {
this.file_name = file_name;
const newImage = { this.file_content = file_content;
userId: userId, this.mime_type = mime_type;
file_name: file.originalname, this.owner = owner;
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();

View file

@ -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<ObjectId | null> {
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<any[]> {
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<string | null> {
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<any[]> {
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<boolean> {
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<number> {
// 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<boolean> {
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<ObjectId | null> {
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<boolean> {
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;

View file

@ -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<string> {
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();

View file

@ -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<Quiz> {
// 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);
}
}