From 9740a803354f443f733d6110c2f81da3b94b1513 Mon Sep 17 00:00:00 2001 From: Gabriel Matte Date: Sun, 10 Nov 2024 10:19:05 -0500 Subject: [PATCH] transfert to ts --- server/app.ts | 33 ++- server/config/{db.ts => db-connection.ts} | 2 +- server/config/{email.ts => emailer.ts} | 2 +- .../{errorCodes.ts => error-codes.ts} | 2 +- .../{folders.ts => folder-controller.ts} | 39 +--- .../{images.ts => image-controller.ts} | 8 +- .../{quiz.ts => quiz-controller.ts} | 6 +- .../{rooms.ts => room-controller.ts} | 4 +- .../{users.ts => user-controller.ts} | 9 +- .../middleware/{AppError.ts => app-error.ts} | 0 .../{errorHandler.ts => error-handler.ts} | 2 +- .../middleware/{jwtToken.ts => jwt-token.ts} | 4 +- server/models/base-model.ts | 8 + server/models/folder-model.ts | 8 + server/models/folders.ts | 202 ------------------ server/models/image-model.ts | 9 + server/models/images.ts | 49 ----- server/models/quiz-model.ts | 9 + server/models/quiz.ts | 157 -------------- server/models/user-model.ts | 8 + server/models/users.ts | 130 ----------- server/package-lock.json | 39 ++++ server/package.json | 2 +- server/repository/base-repository.ts | 44 ++++ server/repository/folder-repository.ts | 10 + server/repository/image-repository.ts | 10 + server/repository/quiz-repository.ts | 10 + server/repository/user-repository.ts | 10 + .../routers/{folders.ts => folder-router.ts} | 4 +- server/routers/{images.ts => image-router.ts} | 2 +- server/routers/{quiz.ts => quiz-router.ts} | 2 +- server/routers/{rooms.ts => room-router.ts} | 2 +- server/routers/{users.ts => user-router.ts} | 2 +- server/tsconfig.json | 33 +-- 34 files changed, 222 insertions(+), 639 deletions(-) rename server/config/{db.ts => db-connection.ts} (94%) rename server/config/{email.ts => emailer.ts} (97%) rename server/constants/{errorCodes.ts => error-codes.ts} (98%) rename server/controllers/{folders.ts => folder-controller.ts} (87%) rename server/controllers/{images.ts => image-controller.ts} (91%) rename server/controllers/{quiz.ts => quiz-controller.ts} (98%) rename server/controllers/{rooms.ts => room-controller.ts} (97%) rename server/controllers/{users.ts => user-controller.ts} (95%) rename server/middleware/{AppError.ts => app-error.ts} (100%) rename server/middleware/{errorHandler.ts => error-handler.ts} (95%) rename server/middleware/{jwtToken.ts => jwt-token.ts} (92%) create mode 100644 server/models/base-model.ts create mode 100644 server/models/folder-model.ts delete mode 100644 server/models/folders.ts create mode 100644 server/models/image-model.ts delete mode 100644 server/models/images.ts create mode 100644 server/models/quiz-model.ts delete mode 100644 server/models/quiz.ts create mode 100644 server/models/user-model.ts delete mode 100644 server/models/users.ts create mode 100644 server/repository/base-repository.ts create mode 100644 server/repository/folder-repository.ts create mode 100644 server/repository/image-repository.ts create mode 100644 server/repository/quiz-repository.ts create mode 100644 server/repository/user-repository.ts rename server/routers/{folders.ts => folder-router.ts} (89%) rename server/routers/{images.ts => image-router.ts} (90%) rename server/routers/{quiz.ts => quiz-router.ts} (94%) rename server/routers/{rooms.ts => room-router.ts} (87%) rename server/routers/{users.ts => user-router.ts} (90%) diff --git a/server/app.ts b/server/app.ts index 20afc89..9a11a88 100644 --- a/server/app.ts +++ b/server/app.ts @@ -4,7 +4,6 @@ import dotenv from 'dotenv'; import cors from 'cors'; import bodyParser from 'body-parser'; import { ServerOptions, Server as SocketIOServer } from 'socket.io'; -import { GlideClient } from '@valkey/valkey-glide'; // Set app defaults const environment: string = process.env.NODE_ENV ?? "production"; @@ -14,12 +13,12 @@ const isDev: boolean = environment === "development"; import setupWebsocket from "./socket/socket"; // Import Database -import db from './config/db'; +import db from './config/db-connection'; // Import Models import Quiz from './models/quiz'; import Folders from './models/folders'; -import Users from './models/users'; +import Users from './models/user-model'; import Images from './models/images'; // Instantiate models @@ -29,28 +28,28 @@ const userModel = new Users(db, foldersModel); const imageModel = new Images(db); // Initialize cache +/* const valkey = await GlideClient.createClient({ addresses: [{ host: process.env.VALKEY_HOST ?? 'localhost', port: Number(process.env.VALKEY_PORT) ?? 6379 }] }); +*/ // Import Controllers -import UsersController from './controllers/users'; -import FoldersController from './controllers/folders'; -import QuizController from './controllers/quiz'; -import ImagesController from './controllers/images'; -import { RoomManager as RoomsController } from './controllers/rooms'; +import UsersController from './controllers/user-controller'; +import FoldersController from './controllers/folder-controller'; +import QuizController from './controllers/quiz-controller'; +import ImagesController from './controllers/image-controller'; +//import { RoomManager as RoomsController } from './controllers/rooms'; // Instantiate Controllers const usersControllerInstance = new UsersController(userModel); const foldersControllerInstance = new FoldersController(foldersModel); const quizControllerInstance = new QuizController(quizModel, foldersModel); const imagesControllerInstance = new ImagesController(imageModel); - -// Initialize valkey before creating rooms controller -const roomsControllerInstance = new RoomsController({}, valkey); +//const roomsControllerInstance = new RoomsController({}, valkey); // Export Controllers @@ -59,18 +58,18 @@ export const controllers = { folders: foldersControllerInstance, quizzes: quizControllerInstance, images: imagesControllerInstance, - rooms: roomsControllerInstance + //rooms: roomsControllerInstance }; // Import Routers -import userRouter from './routers/users'; -import folderRouter from './routers/folders'; -import quizRouter from './routers/quiz'; -import imagesRouter from './routers/images'; +import userRouter from './routers/user-router'; +import folderRouter from './routers/folder-router'; +import quizRouter from './routers/quiz-router'; +import imagesRouter from './routers/image-router'; // Setup environment dotenv.config(); -import errorHandler from "./middleware/errorHandler"; +import errorHandler from "./middleware/error-handler"; // Start app const app: Application = express(); diff --git a/server/config/db.ts b/server/config/db-connection.ts similarity index 94% rename from server/config/db.ts rename to server/config/db-connection.ts index be815b4..307f166 100644 --- a/server/config/db.ts +++ b/server/config/db-connection.ts @@ -22,4 +22,4 @@ export class DBConnection { } } -export default new DBConnection(); \ No newline at end of file +export default DBConnection \ No newline at end of file diff --git a/server/config/email.ts b/server/config/emailer.ts similarity index 97% rename from server/config/email.ts rename to server/config/emailer.ts index 9680168..e3c225e 100644 --- a/server/config/email.ts +++ b/server/config/emailer.ts @@ -43,4 +43,4 @@ class Emailer { } -export default new Emailer() \ No newline at end of file +export default Emailer \ No newline at end of file diff --git a/server/constants/errorCodes.ts b/server/constants/error-codes.ts similarity index 98% rename from server/constants/errorCodes.ts rename to server/constants/error-codes.ts index d480ea6..a9052e8 100644 --- a/server/constants/errorCodes.ts +++ b/server/constants/error-codes.ts @@ -1,4 +1,4 @@ -import type { AppErrorInfos } from "../middleware/AppError"; +import type { AppErrorInfos } from "../middleware/app-error"; import { HttpStatusCode } from "../utils/http-status-codes"; export const UNAUTHORIZED_NO_TOKEN_GIVEN:AppErrorInfos = { diff --git a/server/controllers/folders.ts b/server/controllers/folder-controller.ts similarity index 87% rename from server/controllers/folders.ts rename to server/controllers/folder-controller.ts index a9b8dab..163e686 100644 --- a/server/controllers/folders.ts +++ b/server/controllers/folder-controller.ts @@ -1,41 +1,16 @@ //controller -import AppError from '../middleware/AppError.js'; -import { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, FOLDER_NOT_FOUND, FOLDER_ALREADY_EXISTS, GETTING_FOLDER_ERROR, DELETE_FOLDER_ERROR, UPDATE_FOLDER_ERROR, MOVING_FOLDER_ERROR, DUPLICATE_FOLDER_ERROR, COPY_FOLDER_ERROR } from '../constants/errorCodes'; -import Folders from '../models/folders.js'; +import AppError from '../middleware/app-error.js'; +import { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, FOLDER_NOT_FOUND, FOLDER_ALREADY_EXISTS, GETTING_FOLDER_ERROR, DELETE_FOLDER_ERROR, UPDATE_FOLDER_ERROR, MOVING_FOLDER_ERROR, DUPLICATE_FOLDER_ERROR, COPY_FOLDER_ERROR } from '../constants/error-codes.js'; +import FolderRepository from '../repository/folder-repository.js'; +import Folder from '../models/folder-model.js'; + // controllers must use arrow functions to bind 'this' to the class instance in order to access class properties as callbacks in Express -class FoldersController { - folders:Folders - - constructor(foldersModel) { - this.folders = foldersModel; - } +class FolderController{ /*** * Basic queries */ - create = async (req, res, next) => { - try { - const { title } = req.body; - - if (!title) { - throw new AppError(MISSING_REQUIRED_PARAMETER); - } - - const result = await this.folders.create(title, req.user.userId); - - if (!result) { - throw new AppError(FOLDER_ALREADY_EXISTS); - } - - return res.status(200).json({ - message: 'Dossier créé avec succès.' - }); - - } catch (error) { - return next(error); - } - } getUserFolders = async (req, res, next) => { try { @@ -260,4 +235,4 @@ class FoldersController { } -export default FoldersController \ No newline at end of file +export default FolderController \ No newline at end of file diff --git a/server/controllers/images.ts b/server/controllers/image-controller.ts similarity index 91% rename from server/controllers/images.ts rename to server/controllers/image-controller.ts index 187a61e..fdc4f17 100644 --- a/server/controllers/images.ts +++ b/server/controllers/image-controller.ts @@ -1,8 +1,8 @@ -import AppError from '../middleware/AppError.js'; -import { MISSING_REQUIRED_PARAMETER, IMAGE_NOT_FOUND } from '../constants/errorCodes.js'; +import AppError from '../middleware/app-error.js'; +import { MISSING_REQUIRED_PARAMETER, IMAGE_NOT_FOUND } from '../constants/error-codes.js'; import Images from '../models/images.js'; -class ImagesController { +class ImageController { images:Images constructor(imagesModel) { @@ -54,4 +54,4 @@ class ImagesController { } -export default ImagesController +export default ImageController diff --git a/server/controllers/quiz.ts b/server/controllers/quiz-controller.ts similarity index 98% rename from server/controllers/quiz.ts rename to server/controllers/quiz-controller.ts index fa963c6..ae6a5c9 100644 --- a/server/controllers/quiz.ts +++ b/server/controllers/quiz-controller.ts @@ -1,7 +1,7 @@ -import emailer from '../config/email.js'; +import emailer from '../config/emailer.js'; -import AppError from'../middleware/AppError.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, DUPLICATE_QUIZ_ERROR, COPY_QUIZ_ERROR } from'../constants/errorCodes.js'; +import AppError from'../middleware/app-error.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, DUPLICATE_QUIZ_ERROR, COPY_QUIZ_ERROR } from'../constants/error-codes.js'; import Folders from '../models/folders.js'; import Quiz from '../models/quiz.js'; diff --git a/server/controllers/rooms.ts b/server/controllers/room-controller.ts similarity index 97% rename from server/controllers/rooms.ts rename to server/controllers/room-controller.ts index 91d73ad..8227a6a 100644 --- a/server/controllers/rooms.ts +++ b/server/controllers/room-controller.ts @@ -15,7 +15,7 @@ interface RoomManagerOptions { providerOptions?: ProviderConfig; } -export class RoomManager { +export class RoomController { private valkey: GlideClient; private provider: BaseRoomProvider; @@ -75,4 +75,4 @@ export class RoomManager { } } -module.exports = RoomManager; \ No newline at end of file +export default RoomController; \ No newline at end of file diff --git a/server/controllers/users.ts b/server/controllers/user-controller.ts similarity index 95% rename from server/controllers/users.ts rename to server/controllers/user-controller.ts index 17346e2..de619f3 100644 --- a/server/controllers/users.ts +++ b/server/controllers/user-controller.ts @@ -1,9 +1,8 @@ -import emailer from'../config/email.js'; -import jwt from'../middleware/jwtToken.js'; +import emailer from'../config/emailer.js'; +import jwt from'../middleware/jwt-token.js'; -import AppError from'../middleware/AppError.js'; -import { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR, DELETE_USER_ERROR, USER_NOT_FOUND,USER_CONTROLLER_NOT_INITIALIZED } from'../constants/errorCodes'; -import Users from '../models/users.js' +import AppError from'../middleware/app-error.js'; +import { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR, DELETE_USER_ERROR, USER_NOT_FOUND,USER_CONTROLLER_NOT_INITIALIZED } from'../constants/error-codes.js'; // controllers must use arrow functions to bind 'this' to the class instance in order to access class properties as callbacks in Express class UsersController { diff --git a/server/middleware/AppError.ts b/server/middleware/app-error.ts similarity index 100% rename from server/middleware/AppError.ts rename to server/middleware/app-error.ts diff --git a/server/middleware/errorHandler.ts b/server/middleware/error-handler.ts similarity index 95% rename from server/middleware/errorHandler.ts rename to server/middleware/error-handler.ts index aefaf12..578382d 100644 --- a/server/middleware/errorHandler.ts +++ b/server/middleware/error-handler.ts @@ -1,4 +1,4 @@ -import AppError from './AppError' +import AppError from './app-error' import fs from 'fs'; import { HttpStatusCode } from '../utils/http-status-codes'; diff --git a/server/middleware/jwtToken.ts b/server/middleware/jwt-token.ts similarity index 92% rename from server/middleware/jwtToken.ts rename to server/middleware/jwt-token.ts index 953fbfc..6971570 100644 --- a/server/middleware/jwtToken.ts +++ b/server/middleware/jwt-token.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import dotenv from 'dotenv' -import AppError from './AppError.ts'; -import { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } from '../constants/errorCodes'; +import AppError from './app-error.ts'; +import { UNAUTHORIZED_NO_TOKEN_GIVEN, UNAUTHORIZED_INVALID_TOKEN } from '../constants/error-codes.ts'; dotenv.config(); diff --git a/server/models/base-model.ts b/server/models/base-model.ts new file mode 100644 index 0000000..27b0679 --- /dev/null +++ b/server/models/base-model.ts @@ -0,0 +1,8 @@ +import { ObjectId } from "mongodb"; + +abstract class BaseModel { + id: ObjectId; + abstract validate(): boolean; +} + +export default BaseModel; \ No newline at end of file diff --git a/server/models/folder-model.ts b/server/models/folder-model.ts new file mode 100644 index 0000000..40a23d6 --- /dev/null +++ b/server/models/folder-model.ts @@ -0,0 +1,8 @@ +import BaseModel from './base-model' + +export default class Folder extends BaseModel{ + title:string + validate(): boolean { + return true + } +} \ No newline at end of file diff --git a/server/models/folders.ts b/server/models/folders.ts deleted file mode 100644 index 4a69c11..0000000 --- a/server/models/folders.ts +++ /dev/null @@ -1,202 +0,0 @@ -//model -import type { DBConnection } from '../config/db'; -import type {Quiz} from './quiz' -import {ObjectId} from 'mongodb' -import { generateUniqueTitle } from '../utils/models-utils'; - -class Folders { - - db: DBConnection - quizModel:Quiz - - constructor(db, quizModel) { - this.db = db; - this.quizModel = quizModel; - } - - async create(title, userId) { - - console.log("LOG: create", title, userId); - - if (!title || !userId) { - throw new Error('Missing required parameter(s)'); - } - - await this.db.connect() - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); - - if (existingFolder) { - throw new Error('Folder already exists'); - } - - const newFolder = { - userId: userId, - title: title, - created_at: new Date() - } - - const result = await foldersCollection.insertOne(newFolder); - - return result.insertedId; - } - - async getUserFolders(userId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const result = await foldersCollection.find({ userId: userId }).toArray(); - - return result; - } - - async getOwner(folderId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const folder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId) }); - - return folder?.userId; - } - - // finds all quizzes in a folder - async getContent(folderId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const filesCollection = conn.collection('files'); - - const result = await filesCollection.find({ folderId: folderId }).toArray(); - - return result; - } - - async delete(folderId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const folderResult = await foldersCollection.deleteOne({ _id: ObjectId.createFromHexString(folderId) }); - - if (folderResult.deletedCount != 1) return false; - await this.quizModel.deleteQuizzesByFolderId(folderId); - - return true; - } - - async rename(folderId, newTitle) { - await this.db.connect() - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId) }, { $set: { title: newTitle } }) - - if (result.modifiedCount != 1) return false; - - return true - } - - async duplicate(folderId, userId) { - console.log("LOG: duplicate", folderId, userId); - const conn = this.db.getConnection(); - const foldersCollection = conn.collection('folders'); - - const sourceFolder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId), userId: userId }); - if (!sourceFolder) { - throw new Error(`Folder ${folderId} not found`); - } - - const theUserId = userId; - // Use the utility function to generate a unique title - const newFolderTitle = await generateUniqueTitle(sourceFolder.title, async (title) => { - console.log(`generateUniqueTitle(${title}): userId`, theUserId); - return await foldersCollection.findOne({ title: title, userId: theUserId }); - }); - - const newFolderId = await this.create(newFolderTitle, userId); - - if (!newFolderId) { - throw new Error('Failed to create duplicate folder'); - } - - // copy the quizzes from source folder to destination folder - const content = await this.getContent(folderId); - console.log("folders.duplicate: found content", content); - for (const quiz of content) { - console.log("folders.duplicate: creating quiz (copy)", quiz); - const result = await this.quizModel.create(quiz.title, quiz.content, newFolderId.toString(), userId); - if (!result) { - throw new Error('Failed to create duplicate quiz'); - } - } - - return newFolderId; - } - - async folderExists(title, userId) { - console.log("LOG: folderExists", title, userId); - await this.db.connect(); - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); - - return !!existingFolder; - } - - - async copy(folderId, userId) { - - const sourceFolder = await this.getFolderWithContent(folderId); - - const newFolderId = await this.create(sourceFolder[0].title, userId); - if (!newFolderId) { - throw new Error('Failed to create a new folder.'); - } - for (const quiz of sourceFolder.content) { - await this.quizModel.create(quiz.title, quiz.content, newFolderId, userId); - } - - return newFolderId; - } - - async getFolderById(folderId) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const foldersCollection = conn.collection('folders'); - - const folder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId) }); - - if (!folder) return new Error(`Folder ${folderId} not found`); - - return folder; - } - - - async getFolderWithContent(folderId) { - - - const folder = await this.getFolderById(folderId); - - const content = await this.getContent(folderId); - - return { - ...folder, - content: content - }; - - } - -} - -export default Folders diff --git a/server/models/image-model.ts b/server/models/image-model.ts new file mode 100644 index 0000000..9266359 --- /dev/null +++ b/server/models/image-model.ts @@ -0,0 +1,9 @@ +import BaseModel from './base-model' + +class Image extends BaseModel{ + validate(): boolean { + return true + } +} + +export default Image; \ No newline at end of file diff --git a/server/models/images.ts b/server/models/images.ts deleted file mode 100644 index 5316b9a..0000000 --- a/server/models/images.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type {DBConnection} from '../config/db.js' -import { ObjectId } from 'mongodb'; - -class Images { - db:DBConnection - - constructor(db) { - this.db = db; - } - - async upload(file, userId) { - await this.db.connect() - const conn = this.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; - } - - async get(id) { - await this.db.connect() - const conn = this.db.getConnection(); - - const imagesCollection = conn.collection('images'); - - const result = await imagesCollection.findOne({ _id: ObjectId.createFromHexString(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 Images diff --git a/server/models/quiz-model.ts b/server/models/quiz-model.ts new file mode 100644 index 0000000..669b298 --- /dev/null +++ b/server/models/quiz-model.ts @@ -0,0 +1,9 @@ +import BaseModel from './base-model' + +class Quiz extends BaseModel{ + validate(): boolean { + return true + } +} + +export default Quiz; \ No newline at end of file diff --git a/server/models/quiz.ts b/server/models/quiz.ts deleted file mode 100644 index 8648b41..0000000 --- a/server/models/quiz.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { DBConnection } from '../config/db'; -import { ObjectId } from 'mongodb'; -import { generateUniqueTitle } from '../utils/models-utils' - -export class Quiz { - - db: DBConnection - - constructor(db) { - // console.log("Quiz constructor: db", db) - this.db = db; - } - - async create(title, content, folderId, userId) { - console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`); - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const existingQuiz = await quizCollection.findOne({ title: title, folderId: folderId, userId: userId }) - - if (existingQuiz) { - throw new Error(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`); - } - - const newQuiz = { - folderId: folderId, - userId: userId, - title: title, - content: content, - created_at: new Date(), - updated_at: new Date() - } - - const result = await quizCollection.insertOne(newQuiz); - console.log("quizzes: create insertOne result", result); - - return result.insertedId; - } - - async getOwner(quizId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) }); - - return quiz?.userId; - } - - async getContent(quizId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) }); - - return quiz; - } - - async delete(quizId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const result = await quizCollection.deleteOne({ _id: ObjectId.createFromHexString(quizId) }); - - if (result.deletedCount != 1) return false; - - return true; - } - async deleteQuizzesByFolderId(folderId) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const quizzesCollection = conn.collection('files'); - - // Delete all quizzes with the specified folderId - const result = await quizzesCollection.deleteMany({ folderId: folderId }); - return result.deletedCount > 0; - } - - async update(quizId, newTitle, newContent) { - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const result = await quizCollection.updateOne( - { _id: ObjectId.createFromHexString(quizId) }, - { - $set: { - title: newTitle, - content: newContent, - updated_at: new Date() - } - } - ); - - return result.modifiedCount === 1; - } - - async move(quizId, newFolderId) { - await this.db.connect() - const conn = this.db.getConnection(); - - const quizCollection = conn.collection('files'); - - const result = await quizCollection.updateOne( - { _id: ObjectId.createFromHexString(quizId) }, - { $set: { folderId: newFolderId } } - ); - - if (result.modifiedCount != 1) return false; - - return true - } - - async duplicate(quizId, userId) { - const conn = this.db.getConnection(); - const quizCollection = conn.collection('files'); - - const sourceQuiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId), userId: userId }); - if (!sourceQuiz) { - throw new Error('Quiz not found for quizId: ' + quizId); - } - - // Use the utility function to generate a unique title - const newQuizTitle = await generateUniqueTitle(sourceQuiz.title, async (title) => { - return await quizCollection.findOne({ title: title, folderId: sourceQuiz.folderId, userId: userId }); - }); - - const newQuizId = await this.create(newQuizTitle, sourceQuiz.content, sourceQuiz.folderId, userId); - - if (!newQuizId) { - throw new Error('Failed to create duplicate quiz'); - } - - return newQuizId; - } - - async quizExists(title, userId) { - await this.db.connect(); - const conn = this.db.getConnection(); - - const filesCollection = conn.collection('files'); - const existingFolder = await filesCollection.findOne({ title: title, userId: userId }); - - return existingFolder !== null; - } - -} -export default Quiz; \ No newline at end of file diff --git a/server/models/user-model.ts b/server/models/user-model.ts new file mode 100644 index 0000000..8d26830 --- /dev/null +++ b/server/models/user-model.ts @@ -0,0 +1,8 @@ +import BaseModel from './base-model' + +export default class User extends BaseModel{ + username:string + validate(): boolean { + return true + } +} \ No newline at end of file diff --git a/server/models/users.ts b/server/models/users.ts deleted file mode 100644 index 4f00050..0000000 --- a/server/models/users.ts +++ /dev/null @@ -1,130 +0,0 @@ -//user -import bcrypt from 'bcrypt'; -import AppError from '../middleware/AppError.js' -import { USER_ALREADY_EXISTS } from '../constants/errorCodes.js'; -import type { DBConnection } from '../config/db.js'; -import Folders from './folders.js'; - -class Users { - db:DBConnection - folders:Folders - - constructor(db, foldersModel) { - // console.log("Users constructor: db", db) - this.db = db; - this.folders = foldersModel; - } - - async hashPassword(password) { - return await bcrypt.hash(password, 10) - } - - generatePassword() { - return Math.random().toString(36).slice(-8); - } - - async verify(password, hash) { - return await bcrypt.compare(password, hash) - } - - async register(email, password) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const existingUser = await userCollection.findOne({ email: email }); - - if (existingUser) { - throw new AppError(USER_ALREADY_EXISTS); - } - - const newUser = { - email: email, - password: await this.hashPassword(password), - created_at: new Date() - }; - - const result = await userCollection.insertOne(newUser); - // console.log("userCollection.insertOne() result", result); - const userId = result.insertedId.toString(); - - const folderTitle = 'Dossier par Défaut'; - await this.folders.create(folderTitle, userId); - - return result; - } - - async login(email, password) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const user = await userCollection.findOne({ email: email }); - - if (!user) { - return false; - } - - const passwordMatch = await this.verify(password, user.password); - - if (!passwordMatch) { - return false; - } - - return user; - } - - async resetPassword(email) { - const newPassword = this.generatePassword(); - - return await this.changePassword(email, newPassword); - } - - async changePassword(email, newPassword) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const hashedPassword = await this.hashPassword(newPassword); - - const result = await userCollection.updateOne({ email }, { $set: { password: hashedPassword } }); - - if (result.modifiedCount != 1) return null; - - return newPassword - } - - async delete(email) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const result = await userCollection.deleteOne({ email }); - - if (result.deletedCount != 1) return false; - - return true; - } - - async getId(email) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const user = await userCollection.findOne({ email: email }); - - if (!user) { - return false; - } - - return user._id; - } - -} - -export default Users diff --git a/server/package-lock.json b/server/package-lock.json index bf494d2..8c3365d 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@valkey-ts/client": "^1.5.14", "@valkey/valkey-glide": "^1.1.0", "bcrypt": "^5.1.1", "cors": "^2.8.5", @@ -1433,6 +1434,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@valkey-ts/client": { + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/@valkey-ts/client/-/client-1.5.14.tgz", + "integrity": "sha512-2gPi24bPweUvfUbRsoI8pXOJZQ3fuUghHNxgi7CdAfeEiH0ysIm76jl2F+/Zf4LKkSDTNezmBW0YH359ZPBKXQ==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@valkey-ts/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/@valkey/valkey-glide": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@valkey/valkey-glide/-/valkey-glide-1.1.0.tgz", @@ -2252,6 +2273,15 @@ "node": ">=12" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3148,6 +3178,15 @@ "node": ">=10" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/server/package.json b/server/package.json index 82ec3b0..fb28c27 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "author": "", "license": "MIT", "dependencies": { - "@valkey/valkey-glide": "^1.1.0", + "@valkey-ts/client": "^1.5.14", "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.4.4", diff --git a/server/repository/base-repository.ts b/server/repository/base-repository.ts new file mode 100644 index 0000000..3257e42 --- /dev/null +++ b/server/repository/base-repository.ts @@ -0,0 +1,44 @@ +import { Collection, Document } from 'mongodb'; +import type { DBConnection } from '../config/db-connection'; + +export default class BaseRepository{ + protected db: DBConnection + protected collection: Collection + + constructor(db:DBConnection,collectionName:string) { + db= db; + db.connect(); + this.collection = db.getConnection().collection(collectionName); + } + + async create(item: T|Object): Promise { + const objectToInsert = item as T; + if(!objectToInsert.validate()) return null; + + const result = await this.collection.insertOne(objectToInsert); + return result.insertedId ? objectToInsert : null; + } + + async getAll(): Promise { + const result = await this.collection.find().toArray(); + return result as Object[] as T[]; + } + + async get(id: string): Promise { + const result = await this.collection.findOne({ id: id }); + return result?._id ? result as object as T : null; + } + + async update(id: string, item: T|Object): Promise { + const objectToInsert = item as T; + if(!objectToInsert.validate()) return null; + + const result = await this.collection.updateOne({ id: id }, { $set: objectToInsert }); + return result.modifiedCount ? objectToInsert : null; + } + + async delete(id: string): Promise { + const result = await this.collection.deleteOne({ id: id }); + return result.deletedCount > 0; + } +} \ No newline at end of file diff --git a/server/repository/folder-repository.ts b/server/repository/folder-repository.ts new file mode 100644 index 0000000..fc9b49c --- /dev/null +++ b/server/repository/folder-repository.ts @@ -0,0 +1,10 @@ +import Folder from '../models/folder-model'; +import BaseRepository from './base-repository'; + +class FolderRepository extends BaseRepository { + constructor(db) { + super(db,'folders'); + } +} + +export default FolderRepository; \ No newline at end of file diff --git a/server/repository/image-repository.ts b/server/repository/image-repository.ts new file mode 100644 index 0000000..40d8bfb --- /dev/null +++ b/server/repository/image-repository.ts @@ -0,0 +1,10 @@ +import Image from '../models/image-model'; +import BaseRepository from './base-repository'; + +class ImageRepository extends BaseRepository { + constructor(db) { + super(db,'images'); + } +} + +export default ImageRepository; \ No newline at end of file diff --git a/server/repository/quiz-repository.ts b/server/repository/quiz-repository.ts new file mode 100644 index 0000000..5fc3b93 --- /dev/null +++ b/server/repository/quiz-repository.ts @@ -0,0 +1,10 @@ +import Quiz from '../models/quiz-model'; +import BaseRepository from './base-repository'; + +class QuizRepository extends BaseRepository { + constructor(db) { + super(db,'quizzes'); + } +} + +export default QuizRepository; \ No newline at end of file diff --git a/server/repository/user-repository.ts b/server/repository/user-repository.ts new file mode 100644 index 0000000..90b410e --- /dev/null +++ b/server/repository/user-repository.ts @@ -0,0 +1,10 @@ +import User from "../models/user-model"; +import BaseRepository from "./base-repository"; + +class UserRepository extends BaseRepository { + constructor(db) { + super(db, "users"); + } +} + +export default UserRepository; \ No newline at end of file diff --git a/server/routers/folders.ts b/server/routers/folder-router.ts similarity index 89% rename from server/routers/folders.ts rename to server/routers/folder-router.ts index 2488a41..63d79b9 100644 --- a/server/routers/folders.ts +++ b/server/routers/folder-router.ts @@ -1,5 +1,5 @@ -import express, { Response, Request } from "express"; -import jwt from '../middleware/jwtToken.js'; +import express from "express"; +import jwt from '../middleware/jwt-token.js'; import {controllers} from '../app.js' const folders = controllers.folders diff --git a/server/routers/images.ts b/server/routers/image-router.ts similarity index 90% rename from server/routers/images.ts rename to server/routers/image-router.ts index df1463f..7da050f 100644 --- a/server/routers/images.ts +++ b/server/routers/image-router.ts @@ -1,5 +1,5 @@ import express, { Response, Request } from "express"; -import jwt from '../middleware/jwtToken.js'; +import jwt from '../middleware/jwt-token.js'; import {controllers} from '../app.js' import multer from 'multer'; diff --git a/server/routers/quiz.ts b/server/routers/quiz-router.ts similarity index 94% rename from server/routers/quiz.ts rename to server/routers/quiz-router.ts index be31401..776d2ea 100644 --- a/server/routers/quiz.ts +++ b/server/routers/quiz-router.ts @@ -1,5 +1,5 @@ import express, { Response, Request } from "express"; -import jwt from '../middleware/jwtToken.js'; +import jwt from '../middleware/jwt-token.js'; import {controllers} from '../app.js' const quizzes = controllers.quizzes diff --git a/server/routers/rooms.ts b/server/routers/room-router.ts similarity index 87% rename from server/routers/rooms.ts rename to server/routers/room-router.ts index f3dea77..14c2a79 100644 --- a/server/routers/rooms.ts +++ b/server/routers/room-router.ts @@ -1,5 +1,5 @@ import express, { Response, Request } from "express"; -import jwt from '../middleware/jwtToken.js'; +import jwt from '../middleware/jwt-token.js'; import {controllers} from '../app.js' const roomsController = controllers.rooms diff --git a/server/routers/users.ts b/server/routers/user-router.ts similarity index 90% rename from server/routers/users.ts rename to server/routers/user-router.ts index 38fb2c7..4fbe7fb 100644 --- a/server/routers/users.ts +++ b/server/routers/user-router.ts @@ -1,5 +1,5 @@ import express, { Response, Request } from "express"; -import jwt from '../middleware/jwtToken.js'; +import jwt from '../middleware/jwt-token.js'; import {controllers} from '../app.js' const users = controllers.users diff --git a/server/tsconfig.json b/server/tsconfig.json index f463c69..36cc44a 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,29 +1,12 @@ { "compilerOptions": { - "target": "es2022", - "module": "es2022", - "lib": ["es2017", "esnext.asynciterable"], - "skipLibCheck": true, - "sourceMap": true, - "outDir": "./dist", - "moduleResolution": "node", - "removeComments": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "baseUrl": ".", - "rootDir": "." + "target": "ES6", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true }, - "exclude": ["node_modules"], - "include": ["./**/*.ts"] + "include": ["server/**/*.ts"], + "exclude": ["node_modules"] } \ No newline at end of file