Compare commits

..

No commits in common. "357fd1a271f93424f1c9fa174140aabd2038c658" and "55b6c41148aeaeb37cefc048320c34af9975bc63" have entirely different histories.

15 changed files with 66 additions and 115 deletions

View file

@ -101,12 +101,12 @@ const Dashboard: React.FC = () => {
if (selectedFolderId == '') { if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
//console.log("show all quizzes") console.log("show all quizes")
let quizzes: QuizType[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); const folderQuizzes = await ApiService.getFolderContent(folder._id);
//console.log("folder: ", folder.title, " quiz: ", folderQuizzes); console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
// add the folder.title to the QuizType if the folderQuizzes is an array // add the folder.title to the QuizType if the folderQuizzes is an array
addFolderTitleToQuizzes(folderQuizzes, folder.title); addFolderTitleToQuizzes(folderQuizzes, folder.title);
quizzes = quizzes.concat(folderQuizzes as QuizType[]) quizzes = quizzes.concat(folderQuizzes as QuizType[])
@ -294,25 +294,15 @@ const Dashboard: React.FC = () => {
try { try {
// folderId: string GET THIS FROM CURRENT FOLDER // folderId: string GET THIS FROM CURRENT FOLDER
// currentTitle: string GET THIS FROM CURRENT FOLDER // currentTitle: string GET THIS FROM CURRENT FOLDER
const newTitle = prompt('Entrée le nouveau nom du fichier', folders.find((folder) => folder._id === selectedFolderId)?.title); const newTitle = prompt('Entrée le nouveau nom du fichier', "Nouveau nom de dossier");
if (newTitle) { if (newTitle) {
const renamedFolderId = selectedFolderId; await ApiService.renameFolder(selectedFolderId, newTitle);
const result = await ApiService.renameFolder(selectedFolderId, newTitle);
if (result !== true ) {
window.alert(`Une erreur est survenue: ${result}`);
return;
}
const userFolders = await ApiService.getUserFolders(); const userFolders = await ApiService.getUserFolders();
setFolders(userFolders as FolderType[]); setFolders(userFolders as FolderType[]);
// refresh the page
setSelectedFolderId('');
setSelectedFolderId(renamedFolderId);
} }
} catch (error) { } catch (error) {
console.error('Error renaming folder:', error); console.error('Error renaming folder:', error);
alert('Erreur lors du renommage du dossier: ' + error);
} }
}; };

View file

@ -419,7 +419,7 @@ class ApiService {
*/ */
public async renameFolder(folderId: string, newTitle: string): Promise<ApiResponse> { public async renameFolder(folderId: string, newTitle: string): Promise<ApiResponse> {
try { try {
console.log(`rename folder: folderId: ${folderId}, newTitle: ${newTitle}`);
if (!folderId || !newTitle) { if (!folderId || !newTitle) {
throw new Error(`Le folderId et le nouveau titre sont requis.`); throw new Error(`Le folderId et le nouveau titre sont requis.`);
} }
@ -428,9 +428,7 @@ class ApiService {
const headers = this.constructRequestHeaders(); const headers = this.constructRequestHeaders();
const body = { folderId, newTitle }; const body = { folderId, newTitle };
const result = await axios.put(url, body, { headers: headers }); const result: AxiosResponse = await axios.put(url, body, { headers: headers });
console.log(`rename folder: result: ${result.status}, ${result.data}`);
if (result.status !== 200) { if (result.status !== 200) {
throw new Error(`Le changement de nom de dossier a échoué. Status: ${result.status}`); throw new Error(`Le changement de nom de dossier a échoué. Status: ${result.status}`);
} }
@ -442,7 +440,6 @@ class ApiService {
if (axios.isAxiosError(error)) { if (axios.isAxiosError(error)) {
const err = error as AxiosError; const err = error as AxiosError;
console.log(JSON.stringify(err));
const data = err.response?.data as { error: string } | undefined; const data = err.response?.data as { error: string } | undefined;
return data?.error || 'Erreur serveur inconnue lors de la requête.'; return data?.error || 'Erreur serveur inconnue lors de la requête.';
} }

View file

@ -197,52 +197,32 @@ describe('Folders', () => {
it('should rename a folder and return true', async () => { it('should rename a folder and return true', async () => {
const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
const newTitle = 'New Folder Name'; const newTitle = 'New Folder Name';
const userId = '12345';
// Mock the database response // Mock the database response
collection.updateOne.mockResolvedValue({ modifiedCount: 1 }); collection.updateOne.mockResolvedValue({ modifiedCount: 1 });
const result = await folders.rename(folderId, userId, newTitle); const result = await folders.rename(folderId, newTitle);
expect(db.connect).toHaveBeenCalled(); expect(db.connect).toHaveBeenCalled();
expect(db.collection).toHaveBeenCalledWith('folders'); expect(db.collection).toHaveBeenCalledWith('folders');
// { _id: ObjectId.createFromHexString(folderId), userId: userId }, { $set: { title: newTitle } } expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId), userId: userId }, { $set: { title: newTitle } });
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('should return false if the folder does not exist', async () => { it('should return false if the folder does not exist', async () => {
const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
const newTitle = 'New Folder Name'; const newTitle = 'New Folder Name';
const userId = '12345';
// Mock the database response // Mock the database response
collection.updateOne.mockResolvedValue({ modifiedCount: 0 }); collection.updateOne.mockResolvedValue({ modifiedCount: 0 });
const result = await folders.rename(folderId, userId, newTitle); const result = await folders.rename(folderId, newTitle);
expect(db.connect).toHaveBeenCalled(); expect(db.connect).toHaveBeenCalled();
expect(db.collection).toHaveBeenCalledWith('folders'); expect(db.collection).toHaveBeenCalledWith('folders');
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId), userId: userId }, { $set: { title: newTitle } }); expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should throw an error if the new title is already in use', async () => {
const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
const newTitle = 'Existing Folder';
const userId = '12345';
// Mock the database response
collection.findOne.mockResolvedValue({ title: newTitle });
collection.updateOne.mockResolvedValue({ modifiedCount: 0 });
await expect(folders.rename(folderId, userId, newTitle)).rejects.toThrow(`Folder with name '${newTitle}' already exists.`);
expect(db.connect).toHaveBeenCalled();
expect(db.collection).toHaveBeenCalledWith('folders');
// expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
expect(collection.findOne).toHaveBeenCalledWith({ userId: userId, title: newTitle });
});
}); });
// duplicate // duplicate

View file

@ -29,7 +29,7 @@ exports.UPDATE_PASSWORD_ERROR = {
code: 400 code: 400
} }
exports.DELETE_USER_ERROR = { exports.DELETE_USER_ERROR = {
message: 'Une erreur s\'est produite lors de suppression de l\'utilisateur.', message: 'Une erreur s\'est produite lors de supression de l\'utilisateur.',
code: 400 code: 400
} }
@ -43,15 +43,15 @@ exports.QUIZ_NOT_FOUND = {
code: 404 code: 404
} }
exports.QUIZ_ALREADY_EXISTS = { exports.QUIZ_ALREADY_EXISTS = {
message: 'Le quiz existe déjà.', message: 'Le quiz existe déja.',
code: 400 code: 400
} }
exports.UPDATE_QUIZ_ERROR = { exports.UPDATE_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la mise à jour du quiz.', message: 'Une erreur s\'est produite lors de la mise à jours du quiz.',
code: 400 code: 400
} }
exports.DELETE_QUIZ_ERROR = { exports.DELETE_QUIZ_ERROR = {
message: 'Une erreur s\'est produite lors de la suppression du quiz.', message: 'Une erreur s\'est produite lors de la supression du quiz.',
code: 400 code: 400
} }
exports.GETTING_QUIZ_ERROR = { exports.GETTING_QUIZ_ERROR = {
@ -76,15 +76,15 @@ exports.FOLDER_NOT_FOUND = {
code: 404 code: 404
} }
exports.FOLDER_ALREADY_EXISTS = { exports.FOLDER_ALREADY_EXISTS = {
message: 'Le dossier existe déjà.', message: 'Le dossier existe déja.',
code: 409 code: 400
} }
exports.UPDATE_FOLDER_ERROR = { exports.UPDATE_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la mise à jour du dossier.', message: 'Une erreur s\'est produite lors de la mise à jours du dossier.',
code: 400 code: 400
} }
exports.DELETE_FOLDER_ERROR = { exports.DELETE_FOLDER_ERROR = {
message: 'Une erreur s\'est produite lors de la suppression du dossier.', message: 'Une erreur s\'est produite lors de la supression du dossier.',
code: 400 code: 400
} }
exports.GETTING_FOLDER_ERROR = { exports.GETTING_FOLDER_ERROR = {

View file

@ -126,15 +126,8 @@ class FoldersController {
if (owner != req.user.userId) { if (owner != req.user.userId) {
throw new AppError(FOLDER_NOT_FOUND); throw new AppError(FOLDER_NOT_FOUND);
} }
// Is this the new title already taken by another folder that I own? const result = await this.folders.rename(folderId, newTitle);
const exists = await this.folders.folderExists(newTitle, req.user.userId);
if (exists) {
throw new AppError(FOLDER_ALREADY_EXISTS);
}
const result = await this.folders.rename(folderId, req.user.userId, newTitle);
if (!result) { if (!result) {
throw new AppError(UPDATE_FOLDER_ERROR); throw new AppError(UPDATE_FOLDER_ERROR);

View file

@ -2,8 +2,7 @@ class AppError extends Error {
constructor (errorCode) { constructor (errorCode) {
super(errorCode.message) super(errorCode.message)
this.statusCode = errorCode.code; this.statusCode = errorCode.code;
this.isOperational = true; // Optional: to distinguish operational errors from programming errors
} }
} }
module.exports = AppError; module.exports = AppError;

View file

@ -1,7 +1,8 @@
const AppError = require("./AppError"); const AppError = require("./AppError");
const fs = require('fs'); const fs = require('fs');
const errorHandler = (error, req, res, _next) => { const errorHandler = (error, req, res) => {
console.log("ERROR", error);
if (error instanceof AppError) { if (error instanceof AppError) {
logError(error); logError(error);

View file

@ -10,6 +10,8 @@ class Folders {
async create(title, userId) { async create(title, userId) {
console.log("LOG: create", title, userId);
if (!title || !userId) { if (!title || !userId) {
throw new Error('Missing required parameter(s)'); throw new Error('Missing required parameter(s)');
} }
@ -84,18 +86,13 @@ class Folders {
return true; return true;
} }
async rename(folderId, userId, newTitle) { async rename(folderId, newTitle) {
await this.db.connect() await this.db.connect()
const conn = this.db.getConnection(); const conn = this.db.getConnection();
const foldersCollection = conn.collection('folders'); const foldersCollection = conn.collection('folders');
// see if a folder exists for this user with the new title const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId) }, { $set: { title: newTitle } })
const existingFolder = await foldersCollection.findOne({ title: newTitle, userId: userId });
if (existingFolder) throw new Error(`Folder with name '${newTitle}' already exists.`);
const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId), userId: userId }, { $set: { title: newTitle } })
if (result.modifiedCount != 1) return false; if (result.modifiedCount != 1) return false;
@ -103,6 +100,7 @@ class Folders {
} }
async duplicate(folderId, userId) { async duplicate(folderId, userId) {
console.log("LOG: duplicate", folderId, userId);
const conn = this.db.getConnection(); const conn = this.db.getConnection();
const foldersCollection = conn.collection('folders'); const foldersCollection = conn.collection('folders');
@ -114,7 +112,7 @@ class Folders {
const theUserId = userId; const theUserId = userId;
// Use the utility function to generate a unique title // Use the utility function to generate a unique title
const newFolderTitle = await generateUniqueTitle(sourceFolder.title, async (title) => { const newFolderTitle = await generateUniqueTitle(sourceFolder.title, async (title) => {
// console.log(`generateUniqueTitle(${title}): userId`, theUserId); console.log(`generateUniqueTitle(${title}): userId`, theUserId);
return await foldersCollection.findOne({ title: title, userId: theUserId }); return await foldersCollection.findOne({ title: title, userId: theUserId });
}); });
@ -126,9 +124,9 @@ class Folders {
// copy the quizzes from source folder to destination folder // copy the quizzes from source folder to destination folder
const content = await this.getContent(folderId); const content = await this.getContent(folderId);
// console.log("folders.duplicate: found content", content); console.log("folders.duplicate: found content", content);
for (const quiz of content) { for (const quiz of content) {
// console.log("folders.duplicate: creating quiz (copy)", quiz); console.log("folders.duplicate: creating quiz (copy)", quiz);
const result = await this.quizModel.create(quiz.title, quiz.content, newFolderId.toString(), userId); const result = await this.quizModel.create(quiz.title, quiz.content, newFolderId.toString(), userId);
if (!result) { if (!result) {
throw new Error('Failed to create duplicate quiz'); throw new Error('Failed to create duplicate quiz');
@ -139,12 +137,14 @@ class Folders {
} }
async folderExists(title, userId) { async folderExists(title, userId) {
console.log("LOG: folderExists", title, userId);
await this.db.connect(); await this.db.connect();
const conn = this.db.getConnection(); const conn = this.db.getConnection();
const foldersCollection = conn.collection('folders'); const foldersCollection = conn.collection('folders');
const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); const existingFolder = await foldersCollection.findOne({ title: title, userId: userId });
return existingFolder ? true : false;
return !!existingFolder;
} }

View file

@ -9,7 +9,7 @@ class Quiz {
} }
async create(title, content, folderId, userId) { async create(title, content, folderId, userId) {
// console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`); console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`);
await this.db.connect() await this.db.connect()
const conn = this.db.getConnection(); const conn = this.db.getConnection();
@ -31,7 +31,7 @@ class Quiz {
} }
const result = await quizCollection.insertOne(newQuiz); const result = await quizCollection.insertOne(newQuiz);
// console.log("quizzes: create insertOne result", result); console.log("quizzes: create insertOne result", result);
return result.insertedId; return result.insertedId;
} }

View file

@ -1,6 +1,6 @@
// utils.js // utils.js
async function generateUniqueTitle(baseTitle, existsCallback) { async function generateUniqueTitle(baseTitle, existsCallback) {
// console.log(`generateUniqueTitle(${baseTitle})`); console.log(`generateUniqueTitle(${baseTitle})`);
let newTitle = baseTitle; let newTitle = baseTitle;
let counter = 1; let counter = 1;
@ -19,12 +19,12 @@ async function generateUniqueTitle(baseTitle, existsCallback) {
newTitle = `${baseTitle} (${counter})`; newTitle = `${baseTitle} (${counter})`;
} }
// console.log(`first check of newTitle: ${newTitle}`); console.log(`first check of newTitle: ${newTitle}`);
while (await existsCallback(newTitle)) { while (await existsCallback(newTitle)) {
counter++; counter++;
newTitle = `${baseTitle} (${counter})`; newTitle = `${baseTitle} (${counter})`;
// console.log(`trying newTitle: ${newTitle}`); console.log(`trying newTitle: ${newTitle}`);
} }
return newTitle; return newTitle;

View file

@ -2,17 +2,17 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const jwt = require('../middleware/jwtToken.js'); const jwt = require('../middleware/jwtToken.js');
const folders = require('../app.js').folders; const folders = require('../app.js').folders;
const asyncHandler = require('./routerUtils.js');
router.post("/create", jwt.authenticate, asyncHandler(folders.create)); router.post("/create", jwt.authenticate, folders.create);
router.get("/getUserFolders", jwt.authenticate, asyncHandler(folders.getUserFolders)); router.get("/getUserFolders", jwt.authenticate, folders.getUserFolders);
router.get("/getFolderContent/:folderId", jwt.authenticate, asyncHandler(folders.getFolderContent)); router.get("/getFolderContent/:folderId", jwt.authenticate, folders.getFolderContent);
router.delete("/delete/:folderId", jwt.authenticate, asyncHandler(folders.delete)); router.delete("/delete/:folderId", jwt.authenticate, folders.delete);
router.put("/rename", jwt.authenticate, asyncHandler(folders.rename)); router.put("/rename", jwt.authenticate, folders.rename);
router.post("/duplicate", jwt.authenticate, asyncHandler(folders.duplicate)); //router.post("/duplicate", jwt.authenticate, foldersController.duplicate);
router.post("/duplicate", jwt.authenticate, folders.duplicate);
router.post("/copy/:folderId", jwt.authenticate, asyncHandler(folders.copy)); router.post("/copy/:folderId", jwt.authenticate, folders.copy);
module.exports = router; module.exports = router;

View file

@ -1,7 +1,6 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const images = require('../app.js').images; const images = require('../app.js').images;
const asyncHandler = require('./routerUtils.js');
const jwt = require('../middleware/jwtToken.js'); const jwt = require('../middleware/jwtToken.js');
@ -10,7 +9,7 @@ const multer = require('multer');
const storage = multer.memoryStorage(); const storage = multer.memoryStorage();
const upload = multer({ storage: storage }); const upload = multer({ storage: storage });
router.post("/upload", jwt.authenticate, upload.single('image'), asyncHandler(images.upload)); router.post("/upload", jwt.authenticate, upload.single('image'), images.upload);
router.get("/get/:id", asyncHandler(images.get)); router.get("/get/:id", images.get);
module.exports = router; module.exports = router;

View file

@ -2,22 +2,21 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const quizzes = require('../app.js').quizzes; const quizzes = require('../app.js').quizzes;
const jwt = require('../middleware/jwtToken.js'); const jwt = require('../middleware/jwtToken.js');
const asyncHandler = require('./routerUtils.js');
if (!quizzes) { if (!quizzes) {
console.error("quizzes is not defined"); console.error("quizzes is not defined");
} }
router.post("/create", jwt.authenticate, asyncHandler(quizzes.create)); router.post("/create", jwt.authenticate, quizzes.create);
router.get("/get/:quizId", jwt.authenticate, asyncHandler(asyncHandler(quizzes.get))); router.get("/get/:quizId", jwt.authenticate, quizzes.get);
router.delete("/delete/:quizId", jwt.authenticate, asyncHandler(quizzes.delete)); router.delete("/delete/:quizId", jwt.authenticate, quizzes.delete);
router.put("/update", jwt.authenticate, asyncHandler(quizzes.update)); router.put("/update", jwt.authenticate, quizzes.update);
router.put("/move", jwt.authenticate, asyncHandler(quizzes.move)); router.put("/move", jwt.authenticate, quizzes.move);
router.post("/duplicate", jwt.authenticate, asyncHandler(quizzes.duplicate)); router.post("/duplicate", jwt.authenticate, quizzes.duplicate);
router.post("/copy/:quizId", jwt.authenticate, asyncHandler(quizzes.copy)); router.post("/copy/:quizId", jwt.authenticate, quizzes.copy);
router.put("/Share", jwt.authenticate, asyncHandler(quizzes.share)); router.put("/Share", jwt.authenticate, quizzes.share);
router.get("/getShare/:quizId", jwt.authenticate, asyncHandler(quizzes.getShare)); router.get("/getShare/:quizId", jwt.authenticate, quizzes.getShare);
router.post("/receiveShare", jwt.authenticate, asyncHandler(quizzes.receiveShare)); router.post("/receiveShare", jwt.authenticate, quizzes.receiveShare);
module.exports = router; module.exports = router;

View file

@ -1,6 +0,0 @@
// asyncHandler is a wrapper for async functions that catch errors and pass them to the next middleware
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;

View file

@ -2,12 +2,11 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const users = require('../app.js').users; const users = require('../app.js').users;
const jwt = require('../middleware/jwtToken.js'); const jwt = require('../middleware/jwtToken.js');
const asyncHandler = require('./routerUtils.js');
router.post("/register", asyncHandler(users.register)); router.post("/register", users.register);
router.post("/login", asyncHandler(users.login)); router.post("/login", users.login);
router.post("/reset-password", asyncHandler(users.resetPassword)); router.post("/reset-password", users.resetPassword);
router.post("/change-password", jwt.authenticate, asyncHandler(users.changePassword)); router.post("/change-password", jwt.authenticate, users.changePassword);
router.post("/delete-user", jwt.authenticate, asyncHandler(users.delete)); router.post("/delete-user", jwt.authenticate, users.delete);
module.exports = router; module.exports = router;