mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Quiz model class, using a Repository pattern with mocked repository and tests
This commit is contained in:
parent
83a23e4f8c
commit
7abe517c8f
8 changed files with 476 additions and 159 deletions
125
server/__tests__/quiz.test.ts
Normal file
125
server/__tests__/quiz.test.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { MongoClient, Db, ObjectId } from 'mongodb';
|
||||
import { Quiz } from '../models/quiz';
|
||||
import QuizRepository from '../repositories/quizRepository';
|
||||
|
||||
jest.mock('../repositories/quizRepository');
|
||||
|
||||
describe('Quiz Class', () => {
|
||||
let connection: MongoClient;
|
||||
let db: Db;
|
||||
let quizRepository: QuizRepository;
|
||||
|
||||
beforeAll(async () => {
|
||||
connection = {
|
||||
db: jest.fn().mockReturnThis(),
|
||||
collection: jest.fn().mockReturnThis(),
|
||||
findOne: jest.fn(),
|
||||
insertOne: jest.fn(),
|
||||
deleteOne: jest.fn(),
|
||||
deleteMany: jest.fn(),
|
||||
updateOne: jest.fn(),
|
||||
} as unknown as MongoClient;
|
||||
|
||||
db = connection.db();
|
||||
quizRepository = new QuizRepository();
|
||||
(quizRepository as any).db = { getConnection: () => db };
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should create a new quiz', async () => {
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const insertedId = new ObjectId();
|
||||
jest.spyOn(quizRepository, 'createQuiz').mockResolvedValue(insertedId);
|
||||
|
||||
const result = await quiz.create();
|
||||
|
||||
expect(result).toEqual(insertedId);
|
||||
expect(quizRepository.createQuiz).toHaveBeenCalledWith(quiz);
|
||||
});
|
||||
|
||||
it('should get the owner of a quiz', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
const userId = 'userId';
|
||||
jest.spyOn(quizRepository, 'getOwner').mockResolvedValue(userId);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.getOwner(quizId);
|
||||
|
||||
expect(result).toEqual(userId);
|
||||
expect(quizRepository.getOwner).toHaveBeenCalledWith(quizId);
|
||||
});
|
||||
|
||||
it('should get the content of a quiz', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
const content = 'content';
|
||||
jest.spyOn(quizRepository, 'getContent').mockResolvedValue(content);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.getContent(quizId);
|
||||
|
||||
expect(result).toEqual(content);
|
||||
expect(quizRepository.getContent).toHaveBeenCalledWith(quizId);
|
||||
});
|
||||
|
||||
it('should delete a quiz', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
jest.spyOn(quizRepository, 'delete').mockResolvedValue(true);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.delete(quizId);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(quizRepository.delete).toHaveBeenCalledWith(quizId);
|
||||
});
|
||||
|
||||
it('should update a quiz', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
const updateData = { title: 'new title' };
|
||||
jest.spyOn(quizRepository, 'update').mockResolvedValue(true);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.update(quizId, updateData);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(quizRepository.update).toHaveBeenCalledWith(quizId, updateData);
|
||||
});
|
||||
|
||||
it('should move a quiz to a new folder', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
const newFolderId = 'newFolderId';
|
||||
jest.spyOn(quizRepository, 'move').mockResolvedValue(true);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.move(quizId, newFolderId);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(quizRepository.move).toHaveBeenCalledWith(quizId, newFolderId);
|
||||
});
|
||||
|
||||
it('should duplicate a quiz', async () => {
|
||||
const quizId = new ObjectId().toHexString();
|
||||
const newQuizId = new ObjectId();
|
||||
jest.spyOn(quizRepository, 'duplicate').mockResolvedValue(newQuizId);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.duplicate(quizId);
|
||||
|
||||
expect(result).toEqual(newQuizId);
|
||||
expect(quizRepository.duplicate).toHaveBeenCalledWith(quizId);
|
||||
});
|
||||
|
||||
it('should check if a quiz exists', async () => {
|
||||
const title = 'title';
|
||||
const userId = 'userId';
|
||||
jest.spyOn(quizRepository, 'quizExists').mockResolvedValue(true);
|
||||
|
||||
const quiz = new Quiz('folderId', 'userId', 'title', 'content', quizRepository);
|
||||
const result = await quiz.quizExists(title, userId);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(quizRepository.quizExists).toHaveBeenCalledWith(title, userId);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
const { MongoClient } = require('mongodb');
|
||||
const dotenv = require('dotenv')
|
||||
import { MongoClient, Db } from 'mongodb';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
class DBConnection {
|
||||
private mongoURI: string;
|
||||
private databaseName: string;
|
||||
private connection: MongoClient | null;
|
||||
|
||||
constructor() {
|
||||
this.mongoURI = process.env.MONGO_URI;
|
||||
this.databaseName = process.env.MONGO_DATABASE;
|
||||
this.mongoURI = process.env.MONGO_URI || '';
|
||||
this.databaseName = process.env.MONGO_DATABASE || '';
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
async connect(): Promise<void> {
|
||||
const client = new MongoClient(this.mongoURI);
|
||||
this.connection = await client.connect();
|
||||
}
|
||||
|
||||
getConnection() {
|
||||
getConnection(): Db {
|
||||
if (!this.connection) {
|
||||
throw new Error('Connexion MongoDB non établie');
|
||||
}
|
||||
|
|
@ -24,5 +27,4 @@ class DBConnection {
|
|||
}
|
||||
}
|
||||
|
||||
const instance = new DBConnection();
|
||||
module.exports = instance;
|
||||
export default DBConnection;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
const nodemailer = require('nodemailer');
|
||||
const dotenv = require('dotenv');
|
||||
import nodemailer, { Transporter, SendMailOptions, SentMessageInfo } from 'nodemailer';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
class Emailer {
|
||||
private senderEmail: string;
|
||||
private psw: string;
|
||||
private transporter: Transporter;
|
||||
|
||||
constructor() {
|
||||
this.senderEmail = process.env.SENDER_EMAIL;
|
||||
this.psw = process.env.EMAIL_PSW;
|
||||
this.senderEmail = process.env.SENDER_EMAIL || '';
|
||||
this.psw = process.env.EMAIL_PSW || '';
|
||||
this.transporter = nodemailer.createTransport({
|
||||
service: process.env.EMAIL_SERVICE,
|
||||
service: process.env.EMAIL_SERVICE || '',
|
||||
auth: {
|
||||
user: this.senderEmail,
|
||||
pass: this.psw
|
||||
|
|
@ -17,33 +20,44 @@ class Emailer {
|
|||
});
|
||||
}
|
||||
|
||||
registerConfirmation(email) {
|
||||
this.transporter.sendMail({
|
||||
private handleEmailResult(error: Error | null, info: SentMessageInfo): void {
|
||||
if (error) {
|
||||
console.error('Error sending email:', error);
|
||||
} else {
|
||||
console.log('Email sent:', info.response);
|
||||
}
|
||||
}
|
||||
|
||||
registerConfirmation(email: string): void {
|
||||
const mailOptions: SendMailOptions = {
|
||||
from: this.senderEmail,
|
||||
to: email,
|
||||
subject: 'Confirmation de compte',
|
||||
text: 'Votre compte a été créé avec succès.'
|
||||
});
|
||||
// Add other email options here if needed
|
||||
};
|
||||
this.transporter.sendMail(mailOptions, this.handleEmailResult);
|
||||
}
|
||||
|
||||
newPasswordConfirmation(email,newPassword) {
|
||||
this.transporter.sendMail({
|
||||
newPasswordConfirmation(email: string, newPassword: string): void {
|
||||
const mailOptions: SendMailOptions = {
|
||||
from: this.senderEmail,
|
||||
to: email,
|
||||
subject: 'Mot de passe temporaire',
|
||||
text: 'Votre nouveau mot de passe temporaire est : ' + newPassword
|
||||
});
|
||||
};
|
||||
this.transporter.sendMail(mailOptions, this.handleEmailResult);
|
||||
}
|
||||
|
||||
quizShare(email, link) {
|
||||
this.transporter.sendMail({
|
||||
quizShare(email: string, link: string): void {
|
||||
const mailOptions: SendMailOptions = {
|
||||
from: this.senderEmail,
|
||||
to: email,
|
||||
subject: 'Un quiz vous a été transféré !',
|
||||
text: 'Veuillez suivre ce lien pour ajouter ce quiz à votre compte. '+ link
|
||||
});
|
||||
};
|
||||
this.transporter.sendMail(mailOptions, this.handleEmailResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new Emailer();
|
||||
export default Emailer;
|
||||
|
|
|
|||
|
|
@ -1,133 +1,58 @@
|
|||
const db = require('../config/db.js')
|
||||
const { ObjectId } = require('mongodb');
|
||||
import { ObjectId } from 'mongodb';
|
||||
import QuizRepository from '../repositories/quizRepository';
|
||||
|
||||
class Quiz {
|
||||
export class Quiz {
|
||||
private repository: QuizRepository;
|
||||
folderId: string;
|
||||
userId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
|
||||
async create(title, content, folderId, userId) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const existingQuiz = await quizCollection.findOne({ title: title, folderId: folderId, userId: userId })
|
||||
|
||||
if (existingQuiz) return null;
|
||||
|
||||
const newQuiz = {
|
||||
folderId: folderId,
|
||||
userId: userId,
|
||||
title: title,
|
||||
content: content,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
}
|
||||
|
||||
const result = await quizCollection.insertOne(newQuiz);
|
||||
|
||||
return result.insertedId;
|
||||
constructor(folderId: string, userId: string, title: string, content: string, repository?: QuizRepository) {
|
||||
this.repository = repository || new QuizRepository();
|
||||
this.folderId = folderId;
|
||||
this.userId = userId;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.created_at = new Date();
|
||||
this.updated_at = new Date();
|
||||
}
|
||||
|
||||
async getOwner(quizId) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
return quiz.userId;
|
||||
async create(): Promise<ObjectId | null> {
|
||||
return await this.repository.createQuiz(this);
|
||||
}
|
||||
|
||||
async getContent(quizId) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
return quiz;
|
||||
async getOwner(quizId: string): Promise<string | null> {
|
||||
return await this.repository.getOwner(quizId);
|
||||
}
|
||||
|
||||
async delete(quizId) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.deleteOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
if (result.deletedCount != 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
async deleteQuizzesByFolderId(folderId) {
|
||||
await db.connect();
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizzesCollection = conn.collection('files');
|
||||
|
||||
// Delete all quizzes with the specified folderId
|
||||
await quizzesCollection.deleteMany({ folderId: folderId });
|
||||
async getContent(quizId: string): Promise<string | null> {
|
||||
return await this.repository.getContent(quizId);
|
||||
}
|
||||
|
||||
async update(quizId, newTitle, newContent) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.updateOne({ _id: new ObjectId(quizId) }, { $set: { title: newTitle, content: newContent } });
|
||||
//Ne fonctionne pas si rien n'est chngé dans le quiz
|
||||
//if (result.modifiedCount != 1) return false;
|
||||
|
||||
return true
|
||||
async delete(quizId: string): Promise<boolean> {
|
||||
return await this.repository.delete(quizId);
|
||||
}
|
||||
|
||||
async move(quizId, newFolderId) {
|
||||
await db.connect()
|
||||
const conn = db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.updateOne({ _id: new ObjectId(quizId) }, { $set: { folderId: newFolderId } });
|
||||
|
||||
if (result.modifiedCount != 1) return false;
|
||||
|
||||
return true
|
||||
async deleteQuizzes(folderId: string): Promise<number> {
|
||||
return await this.repository.deleteQuizzes(folderId);
|
||||
}
|
||||
|
||||
async duplicate(quizId, userId) {
|
||||
|
||||
const sourceQuiz = await this.getContent(quizId);
|
||||
|
||||
let newQuizTitle = `${sourceQuiz.title}-copy`;
|
||||
let counter = 1;
|
||||
while (await this.quizExists(newQuizTitle, userId)) {
|
||||
newQuizTitle = `${sourceQuiz.title}-copy(${counter})`;
|
||||
counter++;
|
||||
}
|
||||
//console.log(newQuizTitle);
|
||||
const newQuizId = await this.create(newQuizTitle, sourceQuiz.content,sourceQuiz.folderId, userId);
|
||||
|
||||
if (!newQuizId) {
|
||||
throw new Error('Failed to create a duplicate quiz.');
|
||||
}
|
||||
|
||||
return newQuizId;
|
||||
|
||||
async update(quizId: string, updateData: Partial<Quiz>): Promise<boolean> {
|
||||
return await this.repository.update(quizId, updateData);
|
||||
}
|
||||
|
||||
async quizExists(title, userId) {
|
||||
await db.connect();
|
||||
const conn = db.getConnection();
|
||||
|
||||
const filesCollection = conn.collection('files');
|
||||
const existingFolder = await filesCollection.findOne({ title: title, userId: userId });
|
||||
|
||||
return existingFolder !== null;
|
||||
async move(quizId: string, newFolderId: string): Promise<boolean> {
|
||||
return await this.repository.move(quizId, newFolderId);
|
||||
}
|
||||
|
||||
async duplicate(quizId: string): Promise<ObjectId | null> {
|
||||
return await this.repository.duplicate(quizId);
|
||||
}
|
||||
|
||||
async quizExists(title: string, userId: string): Promise<boolean> {
|
||||
return await this.repository.quizExists(title, userId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Quiz;
|
||||
162
server/package-lock.json
generated
162
server/package-lock.json
generated
|
|
@ -36,6 +36,7 @@
|
|||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"supertest": "^6.3.4",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1665,6 +1666,12 @@
|
|||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
|
|
@ -1898,6 +1905,18 @@
|
|||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/bs-logger": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
|
||||
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-json-stable-stringify": "2.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/bser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
|
||||
|
|
@ -2464,6 +2483,21 @@
|
|||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
"bin": {
|
||||
"ejs": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.574",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz",
|
||||
|
|
@ -2767,6 +2801,36 @@
|
|||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
|
|
@ -3443,6 +3507,24 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
"filelist": "^1.0.4",
|
||||
"minimatch": "^3.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"jake": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
|
|
@ -4261,22 +4343,17 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
|
|
@ -4292,6 +4369,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/makeerror": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
|
||||
|
|
@ -5142,12 +5225,9 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -5744,6 +5824,54 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "29.2.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
|
||||
"integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bs-logger": "^0.2.6",
|
||||
"ejs": "^3.1.10",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"jest-util": "^29.0.0",
|
||||
"json5": "^2.2.3",
|
||||
"lodash.memoize": "^4.1.2",
|
||||
"make-error": "^1.3.6",
|
||||
"semver": "^7.6.3",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-jest": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": ">=7.0.0-beta.0 <8",
|
||||
"@jest/transform": "^29.0.0",
|
||||
"@jest/types": "^29.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"jest": "^29.0.0",
|
||||
"typescript": ">=4.3 <6"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/transform": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/types": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-jest": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"build": "webpack --config webpack.config.js",
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"test": "jest"
|
||||
"test": "jest --colors"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -40,16 +40,18 @@
|
|||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"supertest": "^6.3.4",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"**/__tests__/**/*.js?(x)",
|
||||
"**/?(*.)+(spec|test).js?(x)"
|
||||
"**/__tests__/**/*.ts?(x)",
|
||||
"**/?(*.)+(spec|test).ts?(x)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
121
server/repositories/quizRepository.ts
Normal file
121
server/repositories/quizRepository.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import { Db, ObjectId } from 'mongodb';
|
||||
import DBConnection from '../config/db';
|
||||
import { Quiz } from '../models/quiz';
|
||||
|
||||
class QuizRepository {
|
||||
private db: DBConnection;
|
||||
|
||||
constructor() {
|
||||
this.db = new DBConnection();
|
||||
}
|
||||
|
||||
async createQuiz(quiz: Quiz): Promise<ObjectId | null> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const existingQuiz = await quizCollection.findOne({ title: quiz.title, folderId: quiz.folderId, userId: quiz.userId });
|
||||
|
||||
if (existingQuiz) return null;
|
||||
|
||||
const result = await quizCollection.insertOne(quiz);
|
||||
return result.insertedId;
|
||||
}
|
||||
|
||||
async getOwner(quizId: string): Promise<string | null> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
return quiz ? quiz.userId : null;
|
||||
}
|
||||
|
||||
async getContent(quizId: string): Promise<string | null> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
return quiz ? quiz.content : null;
|
||||
}
|
||||
|
||||
async delete(quizId: string): Promise<boolean> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.deleteOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
return result.deletedCount === 1;
|
||||
}
|
||||
|
||||
async deleteQuizzes(folderId: string): Promise<number> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.deleteMany({ folderId: folderId });
|
||||
return result.deletedCount || 0;
|
||||
}
|
||||
|
||||
async update(quizId: string, updateData: Partial<Quiz>): Promise<boolean> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const result = await quizCollection.updateOne(
|
||||
{ _id: new ObjectId(quizId) },
|
||||
{ $set: { ...updateData, updated_at: new Date() } }
|
||||
);
|
||||
|
||||
return result.modifiedCount === 1;
|
||||
}
|
||||
|
||||
async move(quizId: string, newFolderId: string): Promise<boolean> {
|
||||
return await this.update(quizId, { folderId: newFolderId });
|
||||
}
|
||||
|
||||
async duplicate(quizId: string): Promise<ObjectId | null> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) });
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
const newQuiz = {
|
||||
...quiz,
|
||||
_id: undefined,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
const result = await quizCollection.insertOne(newQuiz);
|
||||
|
||||
return result.insertedId;
|
||||
}
|
||||
|
||||
async quizExists(title: string, userId: string): Promise<boolean> {
|
||||
await this.db.connect();
|
||||
const conn: Db = this.db.getConnection();
|
||||
|
||||
const quizCollection = conn.collection('files');
|
||||
|
||||
const existingQuiz = await quizCollection.findOne({ title: title, userId: userId });
|
||||
|
||||
return existingQuiz !== null;
|
||||
}
|
||||
}
|
||||
|
||||
export default QuizRepository;
|
||||
|
|
@ -107,6 +107,6 @@
|
|||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"include": ["./**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue