mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
transfert to ts
This commit is contained in:
parent
55fe6003c6
commit
9740a80335
34 changed files with 222 additions and 639 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -22,4 +22,4 @@ export class DBConnection {
|
|||
}
|
||||
}
|
||||
|
||||
export default new DBConnection();
|
||||
export default DBConnection
|
||||
|
|
@ -43,4 +43,4 @@ class Emailer {
|
|||
|
||||
}
|
||||
|
||||
export default new Emailer()
|
||||
export default Emailer
|
||||
|
|
@ -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 = {
|
||||
|
|
@ -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
|
||||
export default FolderController
|
||||
|
|
@ -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
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ interface RoomManagerOptions {
|
|||
providerOptions?: ProviderConfig;
|
||||
}
|
||||
|
||||
export class RoomManager {
|
||||
export class RoomController {
|
||||
private valkey: GlideClient;
|
||||
private provider: BaseRoomProvider<RoomInfo>;
|
||||
|
||||
|
|
@ -75,4 +75,4 @@ export class RoomManager {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = RoomManager;
|
||||
export default RoomController;
|
||||
|
|
@ -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 {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import AppError from './AppError'
|
||||
import AppError from './app-error'
|
||||
import fs from 'fs';
|
||||
import { HttpStatusCode } from '../utils/http-status-codes';
|
||||
|
||||
|
|
@ -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();
|
||||
|
||||
8
server/models/base-model.ts
Normal file
8
server/models/base-model.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { ObjectId } from "mongodb";
|
||||
|
||||
abstract class BaseModel {
|
||||
id: ObjectId;
|
||||
abstract validate(): boolean;
|
||||
}
|
||||
|
||||
export default BaseModel;
|
||||
8
server/models/folder-model.ts
Normal file
8
server/models/folder-model.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import BaseModel from './base-model'
|
||||
|
||||
export default class Folder extends BaseModel{
|
||||
title:string
|
||||
validate(): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
9
server/models/image-model.ts
Normal file
9
server/models/image-model.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import BaseModel from './base-model'
|
||||
|
||||
class Image extends BaseModel{
|
||||
validate(): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default Image;
|
||||
|
|
@ -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
|
||||
9
server/models/quiz-model.ts
Normal file
9
server/models/quiz-model.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import BaseModel from './base-model'
|
||||
|
||||
class Quiz extends BaseModel{
|
||||
validate(): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default Quiz;
|
||||
|
|
@ -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;
|
||||
8
server/models/user-model.ts
Normal file
8
server/models/user-model.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import BaseModel from './base-model'
|
||||
|
||||
export default class User extends BaseModel{
|
||||
username:string
|
||||
validate(): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
39
server/package-lock.json
generated
39
server/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
44
server/repository/base-repository.ts
Normal file
44
server/repository/base-repository.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Collection, Document } from 'mongodb';
|
||||
import type { DBConnection } from '../config/db-connection';
|
||||
|
||||
export default class BaseRepository<T extends Document>{
|
||||
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<T | null> {
|
||||
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<T[]> {
|
||||
const result = await this.collection.find().toArray();
|
||||
return result as Object[] as T[];
|
||||
}
|
||||
|
||||
async get(id: string): Promise<T | null> {
|
||||
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<T | null> {
|
||||
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<boolean> {
|
||||
const result = await this.collection.deleteOne({ id: id });
|
||||
return result.deletedCount > 0;
|
||||
}
|
||||
}
|
||||
10
server/repository/folder-repository.ts
Normal file
10
server/repository/folder-repository.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import Folder from '../models/folder-model';
|
||||
import BaseRepository from './base-repository';
|
||||
|
||||
class FolderRepository extends BaseRepository<Folder> {
|
||||
constructor(db) {
|
||||
super(db,'folders');
|
||||
}
|
||||
}
|
||||
|
||||
export default FolderRepository;
|
||||
10
server/repository/image-repository.ts
Normal file
10
server/repository/image-repository.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import Image from '../models/image-model';
|
||||
import BaseRepository from './base-repository';
|
||||
|
||||
class ImageRepository extends BaseRepository<Image> {
|
||||
constructor(db) {
|
||||
super(db,'images');
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageRepository;
|
||||
10
server/repository/quiz-repository.ts
Normal file
10
server/repository/quiz-repository.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import Quiz from '../models/quiz-model';
|
||||
import BaseRepository from './base-repository';
|
||||
|
||||
class QuizRepository extends BaseRepository<Quiz> {
|
||||
constructor(db) {
|
||||
super(db,'quizzes');
|
||||
}
|
||||
}
|
||||
|
||||
export default QuizRepository;
|
||||
10
server/repository/user-repository.ts
Normal file
10
server/repository/user-repository.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import User from "../models/user-model";
|
||||
import BaseRepository from "./base-repository";
|
||||
|
||||
class UserRepository extends BaseRepository<User> {
|
||||
constructor(db) {
|
||||
super(db, "users");
|
||||
}
|
||||
}
|
||||
|
||||
export default UserRepository;
|
||||
|
|
@ -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
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,29 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"lib": ["es2017", "esnext.asynciterable"],
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"target": "ES6",
|
||||
"module": "commonjs",
|
||||
"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": "."
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["./**/*.ts"]
|
||||
"include": ["server/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
Reference in a new issue