From c3a8fbe3c37be16d1607eaa649f3a5e294102015 Mon Sep 17 00:00:00 2001
From: Caaf14 <cata-araya@outlook.com>
Date: Sun, 21 Jan 2024 22:11:16 -0500
Subject: [PATCH 1/7] qcm and qrl model done

---
 server/app/model/database/qcm.ts              |  34 +++
 server/app/model/database/qrl.ts              |  25 ++
 server/app/model/dto/qcm/create-qcm.dto.ts    |  30 ++
 server/app/model/dto/qcm/qcm.dto.constants.ts |   4 +
 server/app/model/dto/qcm/update-qcm.dto.ts    |  34 +++
 server/app/model/dto/qrl/create-qrl.dto.ts    |  20 ++
 server/app/model/dto/qrl/qrl.dto.constants.ts |   2 +
 server/app/model/dto/qrl/update-qrl.dto.ts    |  26 ++
 server/app/services/qcm/qcm.service.spec.ts   | 285 ++++++++++++++++++
 server/app/services/qcm/qcm.service.ts        | 156 ++++++++++
 server/app/services/qrl/qrl.service.spec.ts   | 246 +++++++++++++++
 server/app/services/qrl/qrl.service.ts        | 126 ++++++++
 12 files changed, 988 insertions(+)
 create mode 100644 server/app/model/database/qcm.ts
 create mode 100644 server/app/model/database/qrl.ts
 create mode 100644 server/app/model/dto/qcm/create-qcm.dto.ts
 create mode 100644 server/app/model/dto/qcm/qcm.dto.constants.ts
 create mode 100644 server/app/model/dto/qcm/update-qcm.dto.ts
 create mode 100644 server/app/model/dto/qrl/create-qrl.dto.ts
 create mode 100644 server/app/model/dto/qrl/qrl.dto.constants.ts
 create mode 100644 server/app/model/dto/qrl/update-qrl.dto.ts
 create mode 100644 server/app/services/qcm/qcm.service.spec.ts
 create mode 100644 server/app/services/qcm/qcm.service.ts
 create mode 100644 server/app/services/qrl/qrl.service.spec.ts
 create mode 100644 server/app/services/qrl/qrl.service.ts

diff --git a/server/app/model/database/qcm.ts b/server/app/model/database/qcm.ts
new file mode 100644
index 00000000..9af8c336
--- /dev/null
+++ b/server/app/model/database/qcm.ts
@@ -0,0 +1,34 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { ApiProperty } from '@nestjs/swagger';
+import { Document } from 'mongoose';
+
+export type QcmDocument = Qcm & Document;
+
+export class Choice {
+    choice: string;
+    correct: boolean;
+  }
+
+@Schema()
+export class Qcm {
+    @ApiProperty()
+    @Prop({ required: true })
+    question: string;
+
+    @ApiProperty()
+    @Prop({ required: true })
+    points: number;
+
+    @ApiProperty()
+    @Prop({ required: true })
+    choices: Choice[];
+
+    @ApiProperty()
+    @Prop({ required: true })
+    lastModified: string;
+
+    @ApiProperty()
+    _id?: string;
+}
+
+export const qcmSchema = SchemaFactory.createForClass(Qcm);
diff --git a/server/app/model/database/qrl.ts b/server/app/model/database/qrl.ts
new file mode 100644
index 00000000..da2d7c2e
--- /dev/null
+++ b/server/app/model/database/qrl.ts
@@ -0,0 +1,25 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { ApiProperty } from '@nestjs/swagger';
+import { Document } from 'mongoose';
+
+export type QrlDocument = Qrl & Document;
+
+@Schema()
+export class Qrl {
+    @ApiProperty()
+    @Prop({ required: true })
+    question: string;
+
+    @ApiProperty()
+    @Prop({ required: true })
+    points: number;
+
+    @ApiProperty()
+    @Prop({ required: true })
+    lastModified: string;
+
+    @ApiProperty()
+    _id?: string;
+}
+
+export const qrlSchema = SchemaFactory.createForClass(Qrl);
diff --git a/server/app/model/dto/qcm/create-qcm.dto.ts b/server/app/model/dto/qcm/create-qcm.dto.ts
new file mode 100644
index 00000000..d39f4ae8
--- /dev/null
+++ b/server/app/model/dto/qcm/create-qcm.dto.ts
@@ -0,0 +1,30 @@
+import { Choice } from '@app/model/database/qcm';
+import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
+import { ApiProperty } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { ArrayMaxSize, ArrayMinSize, IsArray, IsInt, IsString, Max, Min, ValidateNested } from 'class-validator';
+
+export class CreateQcmDto {
+    @ApiProperty()
+    @IsString()
+    question: string;
+
+    @ApiProperty()
+    @IsInt()
+    @Min(QCM_MIN_POINTS)
+    @Max(QCM_MAX_POINTS)
+    points: number;
+
+    @ApiProperty({ type: () => Choice, isArray: true })
+    @IsArray()
+    @ArrayMinSize(QCM_MIN_CHOICES)
+    @ArrayMaxSize(QCM_MAX_CHOICES)
+    @ValidateNested({ each: true })
+    @Type(() => Choice)
+    choices: Choice[];
+
+    @ApiProperty()
+    @IsString()
+    lastModified?: string;
+    
+}
diff --git a/server/app/model/dto/qcm/qcm.dto.constants.ts b/server/app/model/dto/qcm/qcm.dto.constants.ts
new file mode 100644
index 00000000..b0968f76
--- /dev/null
+++ b/server/app/model/dto/qcm/qcm.dto.constants.ts
@@ -0,0 +1,4 @@
+export const QCM_MAX_POINTS = 100;
+export const QCM_MIN_POINTS = 10;
+export const QCM_MAX_CHOICES = 4;
+export const QCM_MIN_CHOICES = 2;
\ No newline at end of file
diff --git a/server/app/model/dto/qcm/update-qcm.dto.ts b/server/app/model/dto/qcm/update-qcm.dto.ts
new file mode 100644
index 00000000..d494e1b0
--- /dev/null
+++ b/server/app/model/dto/qcm/update-qcm.dto.ts
@@ -0,0 +1,34 @@
+import { Choice } from '@app/model/database/qcm';
+import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
+import { ApiProperty } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { ArrayMaxSize, ArrayMinSize, IsArray, IsInt, IsNotEmpty, IsOptional, IsString, Max, Min, ValidateNested } from 'class-validator';
+
+export class UpdateQcmDto {
+    @ApiProperty({ required: false })
+    @IsOptional()
+    @IsString()
+    question?: string;
+
+    @ApiProperty({ required: false })
+    @IsOptional()
+    @IsInt()
+    @Min(QCM_MIN_POINTS)
+    @Max(QCM_MAX_POINTS)
+    points?: number;
+
+    @ApiProperty({ type: () => Choice, isArray: true, required: false })
+    @IsOptional()
+    @IsArray()
+    @ArrayMinSize(QCM_MIN_CHOICES)
+    @ArrayMaxSize(QCM_MAX_CHOICES)
+    @ValidateNested({ each: true })
+    @Type(() => Choice)
+    choices?: Choice[];
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    _id?: string;
+    
+}
diff --git a/server/app/model/dto/qrl/create-qrl.dto.ts b/server/app/model/dto/qrl/create-qrl.dto.ts
new file mode 100644
index 00000000..87cc4d40
--- /dev/null
+++ b/server/app/model/dto/qrl/create-qrl.dto.ts
@@ -0,0 +1,20 @@
+import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsInt, IsString, Max, Min } from 'class-validator';
+
+export class CreateQrlDto {
+    @ApiProperty()
+    @IsString()
+    question: string;
+
+    @ApiProperty()
+    @IsInt()
+    @Min(QRL_MIN_POINTS)
+    @Max(QRL_MAX_POINTS)
+    points: number;
+
+    @ApiProperty()
+    @IsString()
+    lastModified: string;
+    
+}
diff --git a/server/app/model/dto/qrl/qrl.dto.constants.ts b/server/app/model/dto/qrl/qrl.dto.constants.ts
new file mode 100644
index 00000000..c7c1155a
--- /dev/null
+++ b/server/app/model/dto/qrl/qrl.dto.constants.ts
@@ -0,0 +1,2 @@
+export const QRL_MAX_POINTS = 100;
+export const QRL_MIN_POINTS = 10;
\ No newline at end of file
diff --git a/server/app/model/dto/qrl/update-qrl.dto.ts b/server/app/model/dto/qrl/update-qrl.dto.ts
new file mode 100644
index 00000000..a0ba07e3
--- /dev/null
+++ b/server/app/model/dto/qrl/update-qrl.dto.ts
@@ -0,0 +1,26 @@
+import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsInt, IsNotEmpty, IsOptional, IsString, Max, Min } from 'class-validator';
+
+export class UpdateQrlDto {
+    @ApiProperty({ required: false })
+    @IsOptional()
+    @IsString()
+    question?: string;
+
+    @ApiProperty({ required: false })
+    @IsOptional()
+    @IsInt()
+    @Min(QRL_MIN_POINTS)
+    @Max(QRL_MAX_POINTS)
+    points?: number;
+
+    @ApiProperty()
+    @IsString()
+    lastModified: string;
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    _id?: string;
+}
diff --git a/server/app/services/qcm/qcm.service.spec.ts b/server/app/services/qcm/qcm.service.spec.ts
new file mode 100644
index 00000000..b254c933
--- /dev/null
+++ b/server/app/services/qcm/qcm.service.spec.ts
@@ -0,0 +1,285 @@
+import { DateService } from '@app/services/date/date.service';
+import { Logger } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { MongoMemoryServer } from 'mongodb-memory-server';
+import { Connection, Model } from 'mongoose';
+import { QcmService } from './qcm.service';
+
+import { Qcm, QcmDocument, qcmSchema } from '@app/model/database/qcm';
+import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
+
+/**
+ * There is two way to test the service :
+ * - Mock the mongoose Model implementation and do what ever we want to do with it (see describe QcmService) or
+ * - Use mongodb memory server implementation (see describe QcmServiceEndToEnd) and let everything go through as if we had a real database
+ *
+ * The second method is generally better because it tests the database queries too.
+ * We will use it more
+ */
+
+describe('QcmService', () => {
+    let service: QcmService;
+    let qcmModel: Model<QcmDocument>;
+
+    beforeEach(async () => {
+        // notice that only the functions we call from the model are mocked
+        // we can´t use sinon because mongoose Model is an interface
+        qcmModel = {
+            countDocuments: jest.fn(),
+            insertMany: jest.fn(),
+            create: jest.fn(),
+            find: jest.fn(),
+            findOne: jest.fn(),
+            deleteOne: jest.fn(),
+            update: jest.fn(),
+            updateOne: jest.fn(),
+        } as unknown as Model<QcmDocument>;
+
+        const module: TestingModule = await Test.createTestingModule({
+            providers: [
+                QcmService,
+                Logger,
+                DateService,
+                {
+                    provide: getModelToken(Qcm.name),
+                    useValue: qcmModel,
+                },
+            ],
+        }).compile();
+
+        service = module.get<QcmService>(QcmService);
+    });
+
+    it('should be defined', () => {
+        expect(service).toBeDefined();
+    });
+
+    it('database should be populated when there is no data', async () => {
+        jest.spyOn(qcmModel, 'countDocuments').mockResolvedValue(0);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await service.start();
+        expect(spyPopulateDB).toHaveBeenCalled();
+    });
+
+    it('database should not be populated when there is some data', async () => {
+        jest.spyOn(qcmModel, 'countDocuments').mockResolvedValue(1);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await service.start();
+        expect(spyPopulateDB).not.toHaveBeenCalled();
+    });
+});
+
+const DELAY_BEFORE_CLOSING_CONNECTION = 200;
+
+describe('QcmServiceEndToEnd', () => {
+    let service: QcmService;
+    let qcmModel: Model<QcmDocument>;
+    let mongoServer: MongoMemoryServer;
+    let connection: Connection;
+
+    beforeEach(async () => {
+        mongoServer = await MongoMemoryServer.create();
+        // notice that only the functions we call from the model are mocked
+        // we can´t use sinon because mongoose Model is an interface
+        const module = await Test.createTestingModule({
+            imports: [
+                MongooseModule.forRootAsync({
+                    useFactory: () => ({
+                        uri: mongoServer.getUri(),
+                    }),
+                }),
+                MongooseModule.forFeature([{ name: Qcm.name, schema: qcmSchema }]),
+            ],
+            providers: [QcmService, Logger, DateService],
+        }).compile();
+
+        service = module.get<QcmService>(QcmService);
+        qcmModel = module.get<Model<QcmDocument>>(getModelToken(Qcm.name));
+        connection = await module.get(getConnectionToken());
+    });
+
+    afterEach((done) => {
+        // The database get auto populated in the constructor
+        // We want to make sur we close the connection after the database got
+        // populated. So we add small delay
+        setTimeout(async () => {
+            await connection.close();
+            await mongoServer.stop();
+            done();
+        }, DELAY_BEFORE_CLOSING_CONNECTION);
+    });
+
+    it('should be defined', () => {
+        expect(service).toBeDefined();
+        expect(qcmModel).toBeDefined();
+    });
+
+    it('start() should populate the database when there is no data', async () => {
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await qcmModel.deleteMany({});
+        await service.start();
+        expect(spyPopulateDB).toHaveBeenCalled();
+    });
+
+    it('start() should not populate the DB when there is some data', async () => {
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        expect(spyPopulateDB).not.toHaveBeenCalled();
+    });
+
+    it('populateDB() should add 2 new qcms', async () => {
+        const eltCountsBefore = await qcmModel.countDocuments();
+        await service.populateDB();
+        const eltCountsAfter = await qcmModel.countDocuments();
+        // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+        expect(eltCountsAfter - eltCountsBefore).toEqual(2);
+    });
+
+    it('getAllQcms() return all qcms in database', async () => {
+        await qcmModel.deleteMany({});
+        expect((await service.getAllQcm()).length).toEqual(0);
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        expect((await service.getAllQcm()).length).toEqual(1);
+    });
+
+    it('getQcmByQuestion() return qcm with the specified question', async () => {
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        expect(await service.getQcmByQuestion(qcm.question)).toEqual(expect.objectContaining(qcm));
+    });
+
+    it('getQcmQuestion() should return qcm question', async () => {
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        const question = await service.getQcmQuestion(qcm.question);
+        expect(question).toEqual(qcm.question);
+    });
+
+    it('getQcmQuestion() should fail if qcm does not exist', async () => {
+        const qcm = getFakeQcm();
+        await expect(service.getQcmQuestion(qcm.question)).rejects.toBeTruthy();
+    });
+
+    it('getQcmChoices() should return qcm question', async () => {
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        const choices = await service.getQcmChoices(qcm.question);
+        expect(choices).toEqual(qcm.choices);
+    });
+
+    it('getQcmChoices() should fail if qcm does not exist', async () => {
+        const qcm = getFakeQcm();
+        await expect(service.getQcmChoices(qcm.question)).rejects.toBeTruthy();
+    });
+
+    it('modifyQcm() should fail if a qcm with same question exists', async () => {
+        const qcm = getFakeQcm();
+        const qcm2 = getFakeQcm2();
+        await qcmModel.create(qcm);
+        await qcmModel.create(qcm2);
+        qcm.question = qcm2.question;
+        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
+    });
+
+    it('modifyQcm() should fail if qcm does not exist', async () => {
+        const qcm = getFakeQcm();
+        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
+    });
+
+    it('modifyQcm() should fail if mongo query failed', async () => {
+        jest.spyOn(qcmModel, 'updateOne').mockRejectedValue('');
+        const qcm = getFakeQcm();
+        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
+    });
+
+    it('deleteQcm() should delete the qcm', async () => {
+        await qcmModel.deleteMany({});
+        const qcm = getFakeQcm();
+        await qcmModel.create(qcm);
+        await service.deleteQcm(qcm.question);
+        expect(await qcmModel.countDocuments()).toEqual(0);
+    });
+
+    it('deleteQcm() should fail if the qcm does not exist', async () => {
+        await qcmModel.deleteMany({});
+        const qcm = getFakeQcm();
+        await expect(service.deleteQcm(qcm.question)).rejects.toBeTruthy();
+    });
+
+    it('deleteQcm() should fail if mongo query failed', async () => {
+        jest.spyOn(qcmModel, 'deleteOne').mockRejectedValue('');
+        const qcm = getFakeQcm();
+        await expect(service.deleteQcm(qcm.question)).rejects.toBeTruthy();
+    });
+
+    it('addQcm() should add the qcm to the DB', async () => {
+        await qcmModel.deleteMany({});
+        const qcm = getFakeQcm();
+        await service.addQcm({ ...qcm, question: 'Question1', points: 50 });
+        expect(await qcmModel.countDocuments()).toEqual(1);
+    });
+
+    it('addQcm() should fail if question already is in DB', async () => {
+        await qcmModel.deleteMany({});
+        const qcm = getFakeQcm();
+
+        await qcmModel.create(qcm);
+        await expect(service.addQcm(qcm)).rejects.toBeTruthy();
+        expect(await qcmModel.countDocuments()).toEqual(1);
+    });
+
+    it('addQcm() should fail if mongo query failed', async () => {
+        jest.spyOn(qcmModel, 'create').mockImplementation(async () => Promise.reject(''));
+        const qcm = getFakeQcm();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50 })).rejects.toBeTruthy();
+    });
+
+    it('addQcm() should fail if the qcm is not a valid', async () => {
+        const qcm = getFakeQcm();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 35,choices: [
+            { choice: 'Oracles', correct: true },
+            { choice: 'Oracle', correct: false },
+            ] })).rejects.toBeTruthy();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 0,choices: [
+            { choice: 'Oracles', correct: true },
+            { choice: 'Oracle', correct: false },
+            ] })).rejects.toBeTruthy();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 110,choices: [
+            { choice: 'Oracles', correct: true },
+            { choice: 'Oracle', correct: false },
+            ] })).rejects.toBeTruthy();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50,choices: [
+            { choice: 'Oracles', correct: true },
+            { choice: 'Oracle', correct: true },
+            ] })).rejects.toBeTruthy();
+        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50,choices: [
+            { choice: 'Oracles', correct: true },
+            { choice: 'Oracle', correct: false },
+            { choice: 'Oracle', correct: false },
+            ] })).rejects.toBeTruthy();     
+    });
+});
+
+const getFakeQcm = (): Qcm => ({
+    question: 'Quel est le nom de ce jeu?',
+    points: 100,
+    choices: [
+        { choice: 'Oracles', correct: true },
+        { choice: 'Oracle', correct: false },
+    ],
+    lastModified: getRandomString(),
+});
+const getFakeQcm2 = (): Qcm => ({
+    question: 'Quel est le nom de ce jeu2?',
+    points: 100,
+    choices: [
+        { choice: 'Oracles', correct: true },
+        { choice: 'Oracle', correct: false },
+    ],
+    lastModified: getRandomString(),
+});
+
+const BASE_36 = 36;
+const getRandomString = (): string => (Math.random() + 1).toString(BASE_36).substring(2);
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
new file mode 100644
index 00000000..a4dbf8bf
--- /dev/null
+++ b/server/app/services/qcm/qcm.service.ts
@@ -0,0 +1,156 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { InjectModel } from '@nestjs/mongoose';
+import { Model } from 'mongoose';
+
+import { Choice, Qcm, QcmDocument } from '@app/model/database/qcm';
+import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
+import { UpdateQcmDto } from '@app/model/dto/qcm/update-qcm.dto';
+import { DateService } from '@app/services/date/date.service';
+
+const MAXIMUM_NUMBER_OF_CREDITS = 6;
+
+@Injectable()
+export class QcmService {
+    constructor(
+        @InjectModel(Qcm.name) public qcmModel: Model<QcmDocument>,
+        private readonly logger: Logger,
+        private readonly dateService: DateService,
+    ) {
+        this.start();
+    }
+
+    async start() {
+        if ((await this.qcmModel.countDocuments()) === 0) {
+            await this.populateDB();
+        }
+    }
+
+    async populateDB(): Promise<void> {
+        const qcmQuestions: CreateQcmDto[] = [
+            {
+                question: 'Dans quelle ville est poly?',
+                points: 10,
+                choices: [
+                    { choice: 'Montreal', correct: true },
+                    { choice: 'Quebec', correct: false },
+                    { choice: 'Sherbrooke', correct: false },
+                    { choice: 'Toronto', correct: false },
+                ],
+                lastModified: this.dateService.currentTime(),
+            },
+            {
+                question: 'Combien de membres dans cette équipe de projet 2?',
+                points: 100,
+                choices: [
+                    { choice: '6', correct: true },
+                    { choice: '5', correct: false },
+                ],
+                lastModified: this.dateService.currentTime(),
+            },
+        ];
+
+        this.logger.log('THIS ADDS DATA TO THE DATABASE, DO NOT USE OTHERWISE');
+        await this.qcmModel.insertMany(qcmQuestions);
+    }
+
+    async getAllQcm(): Promise<Qcm[]> {
+        return await this.qcmModel.find({});
+    }
+
+    async getQcmByQuestion(question: string): Promise<Qcm> {
+        // NB: This can return null if the qcm does not exist, you need to handle it
+        return await this.qcmModel.findOne({ question: question });
+    }
+
+    async addQcm(qcm: CreateQcmDto): Promise<void> {
+
+        if (!this.validateQcm(qcm)) {
+            return Promise.reject('Invalid qcm');
+        }
+        const existingQcm = await this.getQcmByQuestion(qcm.question);
+        if(existingQcm){
+            return Promise.reject('Qcm already exists');
+        }
+        const currentDate = this.dateService.currentTime();
+        const newQcm = { ...qcm, lastModified: currentDate };
+        try {
+            await this.qcmModel.create(newQcm);
+        } catch (error) {
+            return Promise.reject(`Failed to insert course: ${error}`);
+        }
+    }
+
+    async deleteQcm(question: string): Promise<void> {
+        try {
+            const res = await this.qcmModel.deleteOne({ question: question });
+            if (res.deletedCount === 0) {
+                return Promise.reject('Could not find course');
+            }
+        } catch (error) {
+            return Promise.reject(`Failed to delete course: ${error}`);
+        }
+    }
+
+    async modifyQcm(qcm: UpdateQcmDto): Promise<void> {
+        const currentDate = this.dateService.currentTime();
+        const filterQuery = { id: qcm._id };
+        const updatedQcm = { ...qcm, lastModified: currentDate };
+        // Can also use replaceOne if we want to replace the entire object
+        try {
+            const qcmFound = await  this.getQcmByQuestion(qcm.question);
+            if(!qcmFound){
+                return Promise.reject('Qcm does not exist');
+            }    
+            if(qcmFound && qcm._id !== qcmFound._id ){
+                return Promise.reject('Another qcm already has the same question');
+            }
+            const res = await this.qcmModel.replaceOne(filterQuery, updatedQcm);
+            if (res.matchedCount === 0) {
+                return Promise.reject('Could not find qcm');
+            }
+        } catch (error) {
+            return Promise.reject(`Failed to update qcm: ${error}`);
+        }
+    }
+
+    async getQcmQuestion(question: string): Promise<string> {
+        const filterQuery = { question: question };
+        // Only get the choices and not any of the other fields
+        try {
+            const res = await this.qcmModel.findOne(filterQuery, {question: 1});
+            return res.question;
+        } catch (error) {
+            return Promise.reject(`Failed to get data: ${error}`);
+        }
+    }
+    async getQcmChoices(question: string): Promise<Array<Choice>> {
+        const filterQuery = { question: question };
+        // Only get the choices and not any of the other fields
+        try {
+            const res = await this.qcmModel.findOne(filterQuery, {choices: 1});
+            return res.choices;
+        } catch (error) {
+            return Promise.reject(`Failed to get data: ${error}`);
+        }
+    }
+
+    private validateQcm(qcm: CreateQcmDto): boolean {
+        return this.validatePoints(qcm.points) && this.validateChoices(qcm.choices);
+    }
+    private validatePoints(points: number): boolean {
+        return points % 10 === 0 && points >= 10 && points <= 100;
+    }
+    private validateChoices(choices: Choice[]): boolean {
+        let correctChoices = 0;
+        let incorrectChoices = 0;
+        choices.forEach(choice => {
+            if (choice.correct) {
+                correctChoices++;
+            } else {
+                incorrectChoices++;
+            }
+        });
+        let sumChoices = correctChoices + incorrectChoices;
+        return (correctChoices >= 1 && incorrectChoices >= 1) && (sumChoices == 4 || sumChoices == 2);
+    }
+}
diff --git a/server/app/services/qrl/qrl.service.spec.ts b/server/app/services/qrl/qrl.service.spec.ts
new file mode 100644
index 00000000..7432290b
--- /dev/null
+++ b/server/app/services/qrl/qrl.service.spec.ts
@@ -0,0 +1,246 @@
+import { DateService } from '@app/services/date/date.service';
+import { Logger } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { MongoMemoryServer } from 'mongodb-memory-server';
+import { Connection, Model } from 'mongoose';
+import { QrlService } from './qrl.service';
+
+import { Qrl, QrlDocument, qrlSchema } from '@app/model/database/qrl';
+import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
+
+/**
+ * There is two way to test the service :
+ * - Mock the mongoose Model implementation and do what ever we want to do with it (see describe QrlService) or
+ * - Use mongodb memory server implementation (see describe QrlServiceEndToEnd) and let everything go through as if we had a real database
+ *
+ * The second method is generally better because it tests the database queries too.
+ * We will use it more
+ */
+
+describe('QrlService', () => {
+    let service: QrlService;
+    let qrlModel: Model<QrlDocument>;
+
+    beforeEach(async () => {
+        // notice that only the functions we call from the model are mocked
+        // we can´t use sinon because mongoose Model is an interface
+        qrlModel = {
+            countDocuments: jest.fn(),
+            insertMany: jest.fn(),
+            create: jest.fn(),
+            find: jest.fn(),
+            findOne: jest.fn(),
+            deleteOne: jest.fn(),
+            update: jest.fn(),
+            updateOne: jest.fn(),
+        } as unknown as Model<QrlDocument>;
+
+        const module: TestingModule = await Test.createTestingModule({
+            providers: [
+                QrlService,
+                Logger,
+                DateService,
+                {
+                    provide: getModelToken(Qrl.name),
+                    useValue: qrlModel,
+                },
+            ],
+        }).compile();
+
+        service = module.get<QrlService>(QrlService);
+    });
+
+    it('should be defined', () => {
+        expect(service).toBeDefined();
+    });
+
+    it('database should be populated when there is no data', async () => {
+        jest.spyOn(qrlModel, 'countDocuments').mockResolvedValue(0);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await service.start();
+        expect(spyPopulateDB).toHaveBeenCalled();
+    });
+
+    it('database should not be populated when there is some data', async () => {
+        jest.spyOn(qrlModel, 'countDocuments').mockResolvedValue(1);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await service.start();
+        expect(spyPopulateDB).not.toHaveBeenCalled();
+    });
+});
+
+const DELAY_BEFORE_CLOSING_CONNECTION = 200;
+
+describe('QrlServiceEndToEnd', () => {
+    let service: QrlService;
+    let qrlModel: Model<QrlDocument>;
+    let mongoServer: MongoMemoryServer;
+    let connection: Connection;
+
+    beforeEach(async () => {
+        mongoServer = await MongoMemoryServer.create();
+        // notice that only the functions we call from the model are mocked
+        // we can´t use sinon because mongoose Model is an interface
+        const module = await Test.createTestingModule({
+            imports: [
+                MongooseModule.forRootAsync({
+                    useFactory: () => ({
+                        uri: mongoServer.getUri(),
+                    }),
+                }),
+                MongooseModule.forFeature([{ name: Qrl.name, schema: qrlSchema }]),
+            ],
+            providers: [QrlService, Logger, DateService],
+        }).compile();
+
+        service = module.get<QrlService>(QrlService);
+        qrlModel = module.get<Model<QrlDocument>>(getModelToken(Qrl.name));
+        connection = await module.get(getConnectionToken());
+    });
+
+    afterEach((done) => {
+        // The database get auto populated in the constructor
+        // We want to make sur we close the connection after the database got
+        // populated. So we add small delay
+        setTimeout(async () => {
+            await connection.close();
+            await mongoServer.stop();
+            done();
+        }, DELAY_BEFORE_CLOSING_CONNECTION);
+    });
+
+    it('should be defined', () => {
+        expect(service).toBeDefined();
+        expect(qrlModel).toBeDefined();
+    });
+
+    it('start() should populate the database when there is no data', async () => {
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        await qrlModel.deleteMany({});
+        await service.start();
+        expect(spyPopulateDB).toHaveBeenCalled();
+    });
+
+    it('start() should not populate the DB when there is some data', async () => {
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        const spyPopulateDB = jest.spyOn(service, 'populateDB');
+        expect(spyPopulateDB).not.toHaveBeenCalled();
+    });
+
+    it('populateDB() should add 2 new qrls', async () => {
+        const eltCountsBefore = await qrlModel.countDocuments();
+        await service.populateDB();
+        const eltCountsAfter = await qrlModel.countDocuments();
+        // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+        expect(eltCountsAfter - eltCountsBefore).toEqual(2);
+    });
+
+    it('getAllQrl() return all qrls in database', async () => {
+        await qrlModel.deleteMany({});
+        expect((await service.getAllQrl()).length).toEqual(0);
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        expect((await service.getAllQrl()).length).toEqual(1);
+    });
+
+    it('getQrlByQuestion() return qrl with the specified question', async () => {
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        expect(await service.getQrlByQuestion(qrl.question)).toEqual(expect.objectContaining(qrl));
+    });
+
+    it('getQrlQuestion() should return qrl question', async () => {
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        const question = await service.getQrlQuestion(qrl.question);
+        expect(question).toEqual(qrl.question);
+    });
+
+    it('getQrlQuestion() should fail if qrl does not exist', async () => {
+        const qrl = getFakeQrl();
+        await expect(service.getQrlQuestion(qrl.question)).rejects.toBeTruthy();
+    });
+
+    it('modifyQrl() should fail if a qrl with same question exists', async () => {
+        const qrl = getFakeQrl();
+        const qrl2 = getFakeQrl2();
+        await qrlModel.create(qrl);
+        await qrlModel.create(qrl2);
+        qrl.question = qrl2.question;
+        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
+    });
+
+    it('modifyQrl() should fail if qrl does not exist', async () => {
+        const qrl = getFakeQrl();
+        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
+    });
+
+    it('modifyQrl() should fail if mongo query failed', async () => {
+        jest.spyOn(qrlModel, 'updateOne').mockRejectedValue('');
+        const qrl = getFakeQrl();
+        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
+    });
+
+    it('deleteQrl() should delete the qrl', async () => {
+        await qrlModel.deleteMany({});
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        await service.deleteQrl(qrl.question);
+        expect(await qrlModel.countDocuments()).toEqual(0);
+    });
+
+    it('deleteQrl() should fail if the qrl does not exist', async () => {
+        await qrlModel.deleteMany({});
+        const qrl = getFakeQrl();
+        await expect(service.deleteQrl(qrl.question)).rejects.toBeTruthy();
+    });
+
+    it('deleteQrl() should fail if mongo query failed', async () => {
+        jest.spyOn(qrlModel, 'deleteOne').mockRejectedValue('');
+        const qrl = getFakeQrl();
+        await expect(service.deleteQrl(qrl.question)).rejects.toBeTruthy();
+    });
+
+    it('addQrl() should add the qrl to the DB', async () => {
+        await qrlModel.deleteMany({});
+        const qrl = getFakeQrl();
+        await service.addQrl(qrl);
+        expect(await qrlModel.countDocuments()).toEqual(1);
+    });
+
+    it('addQrl() should fail if question already is in DB', async () => {
+        await qrlModel.deleteMany({});
+        const qrl = getFakeQrl();
+        await qrlModel.create(qrl);
+        await expect(service.addQrl(qrl)).rejects.toBeTruthy();
+        expect(await qrlModel.countDocuments()).toEqual(1);
+    });
+
+    it('addQrl() should fail if mongo query failed', async () => {
+        jest.spyOn(qrlModel, 'create').mockImplementation(async () => Promise.reject(''));
+        const qrl = getFakeQrl();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 50 })).rejects.toBeTruthy();
+    });
+
+    it('addQrl() should fail if the qrl is not a valid', async () => {
+        const qrl = getFakeQrl();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 35})).rejects.toBeTruthy();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 0})).rejects.toBeTruthy();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 110})).rejects.toBeTruthy();  
+    });
+});
+
+const getFakeQrl = (): Qrl => ({
+    question: 'Quel est le nom de ce jeu?',
+    points: 100,
+    lastModified: getRandomString(),
+});
+const getFakeQrl2 = (): Qrl => ({
+    question: 'Quel est le nom de ce jeu2?',
+    points: 100,
+    lastModified: getRandomString(),
+});
+
+const BASE_36 = 36;
+const getRandomString = (): string => (Math.random() + 1).toString(BASE_36).substring(2);
diff --git a/server/app/services/qrl/qrl.service.ts b/server/app/services/qrl/qrl.service.ts
new file mode 100644
index 00000000..dc9afaf6
--- /dev/null
+++ b/server/app/services/qrl/qrl.service.ts
@@ -0,0 +1,126 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { InjectModel } from '@nestjs/mongoose';
+import { Model } from 'mongoose';
+
+import { Qrl, QrlDocument } from '@app/model/database/qrl';
+import { CreateQrlDto } from '@app/model/dto/qrl/create-qrl.dto';
+import { UpdateQrlDto } from '@app/model/dto/qrl/update-qrl.dto';
+import { DateService } from '@app/services/date/date.service';
+
+const MAXIMUM_NUMBER_OF_CREDITS = 6;
+
+@Injectable()
+export class QrlService {
+    constructor(
+        @InjectModel(Qrl.name) public qrlModel: Model<QrlDocument>,
+        private readonly logger: Logger,
+        private readonly dateService: DateService,
+    ) {
+        this.start();
+    }
+
+    async start() {
+        if ((await this.qrlModel.countDocuments()) === 0) {
+            await this.populateDB();
+        }
+    }
+
+    async populateDB(): Promise<void> {
+        const qrlQuestions: CreateQrlDto[] = [
+            {
+                question: 'Dans quelle ville est poly?',
+                points: 10,
+                lastModified: this.dateService.currentTime(),
+            },
+            {
+                question: 'Combien de membres dans cette équipe de projet 2?',
+                points: 100,
+                lastModified: this.dateService.currentTime(),
+            },
+        ];
+
+        this.logger.log('THIS ADDS DATA TO THE DATABASE, DO NOT USE OTHERWISE');
+        await this.qrlModel.insertMany(qrlQuestions);
+    }
+
+    async getAllQrl(): Promise<Qrl[]> {
+        return await this.qrlModel.find({});
+    }
+
+    async getQrlById(id: string): Promise<Qrl> {
+        // NB: This can return null if the qrl does not exist, you need to handle it
+        return await this.qrlModel.findById(id);
+    }
+    async getQrlByQuestion(question: string): Promise<Qrl> {
+        // NB: This can return null if the qrl does not exist, you need to handle it
+        return await this.qrlModel.findOne({ question: question });
+    }
+
+    async addQrl(qrl: CreateQrlDto): Promise<void> {
+        if (!this.validateQrl(qrl)) {
+            return Promise.reject('Invalid qrl');
+        }
+        const existingQrl = await this.getQrlByQuestion(qrl.question);
+        if(existingQrl){
+            return Promise.reject('Qrl already exists');
+        }
+        const currentDate = this.dateService.currentTime();
+        const newQrl = { ...qrl, lastModified: currentDate };
+        try {
+            await this.qrlModel.create(newQrl);
+        } catch (error) {
+            return Promise.reject(`Failed to insert course: ${error}`);
+        }
+    }
+
+    async deleteQrl(question: string): Promise<void> {
+        try {
+            const res = await this.qrlModel.deleteOne({ question: question });
+            if (res.deletedCount === 0) {
+                return Promise.reject('Could not find course');
+            }
+        } catch (error) {
+            return Promise.reject(`Failed to delete course: ${error}`);
+        }
+    }
+
+    async modifyQrl(qrl: UpdateQrlDto): Promise<void> {
+        const currentDate = this.dateService.currentTime();
+        const filterQuery = { question: qrl.question };
+        const updatedQrl = { ...qrl, lastModified: currentDate };
+        // Can also use replaceOne if we want to replace the entire object
+        try {
+            const qcmFound = await  this.getQrlByQuestion(qrl.question);
+            if(!qcmFound){
+                return Promise.reject('Qcm does not exist');
+            }    
+            if(qcmFound && qrl._id !== qcmFound._id ){
+                return Promise.reject('Another qcm already has the same question');
+            }
+            const res = await this.qrlModel.replaceOne(filterQuery, updatedQrl);
+            if (res.matchedCount === 0) {
+                return Promise.reject('Could not find qrl');
+            }
+        } catch (error) {
+            return Promise.reject(`Failed to update qrl: ${error}`);
+        }
+    }
+
+    async getQrlQuestion(question: string): Promise<string> {
+        const filterQuery = { question: question };
+        // Only get the choices and not any of the other fields
+        try {
+            const res = await this.qrlModel.findOne(filterQuery, {question: 1});
+            return res.question;
+        } catch (error) {
+            return Promise.reject(`Failed to get data: ${error}`);
+        }
+    }
+
+    private validateQrl(qrl: CreateQrlDto): boolean {
+        return this.validatePoints(qrl.points);
+    }
+    private validatePoints(points: number): boolean {
+        return points % 10 === 0 && points >= 10 && points <= 100;
+    }
+}
-- 
GitLab


From 19c7102352ce04da730cbc0d1bf0e5035365d940 Mon Sep 17 00:00:00 2001
From: Caaf14 <cata-araya@outlook.com>
Date: Sun, 21 Jan 2024 22:28:57 -0500
Subject: [PATCH 2/7] remove magic number

---
 server/app/services/qcm/qcm.service.spec.ts | 18 +-----------------
 server/app/services/qcm/qcm.service.ts      | 11 +++--------
 server/app/services/qrl/qrl.service.spec.ts | 18 +-----------------
 server/app/services/qrl/qrl.service.ts      | 19 +++++++------------
 4 files changed, 12 insertions(+), 54 deletions(-)

diff --git a/server/app/services/qcm/qcm.service.spec.ts b/server/app/services/qcm/qcm.service.spec.ts
index b254c933..ee615586 100644
--- a/server/app/services/qcm/qcm.service.spec.ts
+++ b/server/app/services/qcm/qcm.service.spec.ts
@@ -8,22 +8,12 @@ import { QcmService } from './qcm.service';
 import { Qcm, QcmDocument, qcmSchema } from '@app/model/database/qcm';
 import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
 
-/**
- * There is two way to test the service :
- * - Mock the mongoose Model implementation and do what ever we want to do with it (see describe QcmService) or
- * - Use mongodb memory server implementation (see describe QcmServiceEndToEnd) and let everything go through as if we had a real database
- *
- * The second method is generally better because it tests the database queries too.
- * We will use it more
- */
-
 describe('QcmService', () => {
     let service: QcmService;
     let qcmModel: Model<QcmDocument>;
 
     beforeEach(async () => {
-        // notice that only the functions we call from the model are mocked
-        // we can´t use sinon because mongoose Model is an interface
+
         qcmModel = {
             countDocuments: jest.fn(),
             insertMany: jest.fn(),
@@ -79,8 +69,6 @@ describe('QcmServiceEndToEnd', () => {
 
     beforeEach(async () => {
         mongoServer = await MongoMemoryServer.create();
-        // notice that only the functions we call from the model are mocked
-        // we can´t use sinon because mongoose Model is an interface
         const module = await Test.createTestingModule({
             imports: [
                 MongooseModule.forRootAsync({
@@ -99,9 +87,6 @@ describe('QcmServiceEndToEnd', () => {
     });
 
     afterEach((done) => {
-        // The database get auto populated in the constructor
-        // We want to make sur we close the connection after the database got
-        // populated. So we add small delay
         setTimeout(async () => {
             await connection.close();
             await mongoServer.stop();
@@ -132,7 +117,6 @@ describe('QcmServiceEndToEnd', () => {
         const eltCountsBefore = await qcmModel.countDocuments();
         await service.populateDB();
         const eltCountsAfter = await qcmModel.countDocuments();
-        // eslint-disable-next-line @typescript-eslint/no-magic-numbers
         expect(eltCountsAfter - eltCountsBefore).toEqual(2);
     });
 
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
index a4dbf8bf..0c0cef09 100644
--- a/server/app/services/qcm/qcm.service.ts
+++ b/server/app/services/qcm/qcm.service.ts
@@ -4,11 +4,10 @@ import { Model } from 'mongoose';
 
 import { Choice, Qcm, QcmDocument } from '@app/model/database/qcm';
 import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
+import { QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
 import { UpdateQcmDto } from '@app/model/dto/qcm/update-qcm.dto';
 import { DateService } from '@app/services/date/date.service';
 
-const MAXIMUM_NUMBER_OF_CREDITS = 6;
-
 @Injectable()
 export class QcmService {
     constructor(
@@ -58,7 +57,6 @@ export class QcmService {
     }
 
     async getQcmByQuestion(question: string): Promise<Qcm> {
-        // NB: This can return null if the qcm does not exist, you need to handle it
         return await this.qcmModel.findOne({ question: question });
     }
 
@@ -95,7 +93,6 @@ export class QcmService {
         const currentDate = this.dateService.currentTime();
         const filterQuery = { id: qcm._id };
         const updatedQcm = { ...qcm, lastModified: currentDate };
-        // Can also use replaceOne if we want to replace the entire object
         try {
             const qcmFound = await  this.getQcmByQuestion(qcm.question);
             if(!qcmFound){
@@ -115,7 +112,6 @@ export class QcmService {
 
     async getQcmQuestion(question: string): Promise<string> {
         const filterQuery = { question: question };
-        // Only get the choices and not any of the other fields
         try {
             const res = await this.qcmModel.findOne(filterQuery, {question: 1});
             return res.question;
@@ -125,7 +121,6 @@ export class QcmService {
     }
     async getQcmChoices(question: string): Promise<Array<Choice>> {
         const filterQuery = { question: question };
-        // Only get the choices and not any of the other fields
         try {
             const res = await this.qcmModel.findOne(filterQuery, {choices: 1});
             return res.choices;
@@ -138,7 +133,7 @@ export class QcmService {
         return this.validatePoints(qcm.points) && this.validateChoices(qcm.choices);
     }
     private validatePoints(points: number): boolean {
-        return points % 10 === 0 && points >= 10 && points <= 100;
+        return points % 10 === 0 && points >= QCM_MIN_POINTS && points <= QCM_MAX_POINTS;
     }
     private validateChoices(choices: Choice[]): boolean {
         let correctChoices = 0;
@@ -151,6 +146,6 @@ export class QcmService {
             }
         });
         let sumChoices = correctChoices + incorrectChoices;
-        return (correctChoices >= 1 && incorrectChoices >= 1) && (sumChoices == 4 || sumChoices == 2);
+        return (correctChoices >= 1 && incorrectChoices >= 1) && (sumChoices == QCM_MIN_CHOICES || sumChoices == QCM_MAX_POINTS);
     }
 }
diff --git a/server/app/services/qrl/qrl.service.spec.ts b/server/app/services/qrl/qrl.service.spec.ts
index 7432290b..236b6b1d 100644
--- a/server/app/services/qrl/qrl.service.spec.ts
+++ b/server/app/services/qrl/qrl.service.spec.ts
@@ -8,22 +8,12 @@ import { QrlService } from './qrl.service';
 import { Qrl, QrlDocument, qrlSchema } from '@app/model/database/qrl';
 import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
 
-/**
- * There is two way to test the service :
- * - Mock the mongoose Model implementation and do what ever we want to do with it (see describe QrlService) or
- * - Use mongodb memory server implementation (see describe QrlServiceEndToEnd) and let everything go through as if we had a real database
- *
- * The second method is generally better because it tests the database queries too.
- * We will use it more
- */
-
 describe('QrlService', () => {
     let service: QrlService;
     let qrlModel: Model<QrlDocument>;
 
     beforeEach(async () => {
-        // notice that only the functions we call from the model are mocked
-        // we can´t use sinon because mongoose Model is an interface
+
         qrlModel = {
             countDocuments: jest.fn(),
             insertMany: jest.fn(),
@@ -79,8 +69,6 @@ describe('QrlServiceEndToEnd', () => {
 
     beforeEach(async () => {
         mongoServer = await MongoMemoryServer.create();
-        // notice that only the functions we call from the model are mocked
-        // we can´t use sinon because mongoose Model is an interface
         const module = await Test.createTestingModule({
             imports: [
                 MongooseModule.forRootAsync({
@@ -99,9 +87,6 @@ describe('QrlServiceEndToEnd', () => {
     });
 
     afterEach((done) => {
-        // The database get auto populated in the constructor
-        // We want to make sur we close the connection after the database got
-        // populated. So we add small delay
         setTimeout(async () => {
             await connection.close();
             await mongoServer.stop();
@@ -132,7 +117,6 @@ describe('QrlServiceEndToEnd', () => {
         const eltCountsBefore = await qrlModel.countDocuments();
         await service.populateDB();
         const eltCountsAfter = await qrlModel.countDocuments();
-        // eslint-disable-next-line @typescript-eslint/no-magic-numbers
         expect(eltCountsAfter - eltCountsBefore).toEqual(2);
     });
 
diff --git a/server/app/services/qrl/qrl.service.ts b/server/app/services/qrl/qrl.service.ts
index dc9afaf6..541a0339 100644
--- a/server/app/services/qrl/qrl.service.ts
+++ b/server/app/services/qrl/qrl.service.ts
@@ -4,11 +4,10 @@ import { Model } from 'mongoose';
 
 import { Qrl, QrlDocument } from '@app/model/database/qrl';
 import { CreateQrlDto } from '@app/model/dto/qrl/create-qrl.dto';
+import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
 import { UpdateQrlDto } from '@app/model/dto/qrl/update-qrl.dto';
 import { DateService } from '@app/services/date/date.service';
 
-const MAXIMUM_NUMBER_OF_CREDITS = 6;
-
 @Injectable()
 export class QrlService {
     constructor(
@@ -48,11 +47,9 @@ export class QrlService {
     }
 
     async getQrlById(id: string): Promise<Qrl> {
-        // NB: This can return null if the qrl does not exist, you need to handle it
         return await this.qrlModel.findById(id);
     }
     async getQrlByQuestion(question: string): Promise<Qrl> {
-        // NB: This can return null if the qrl does not exist, you need to handle it
         return await this.qrlModel.findOne({ question: question });
     }
 
@@ -88,14 +85,13 @@ export class QrlService {
         const currentDate = this.dateService.currentTime();
         const filterQuery = { question: qrl.question };
         const updatedQrl = { ...qrl, lastModified: currentDate };
-        // Can also use replaceOne if we want to replace the entire object
         try {
-            const qcmFound = await  this.getQrlByQuestion(qrl.question);
-            if(!qcmFound){
-                return Promise.reject('Qcm does not exist');
+            const qrlFound = await  this.getQrlByQuestion(qrl.question);
+            if(!qrlFound){
+                return Promise.reject('Qrl does not exist');
             }    
-            if(qcmFound && qrl._id !== qcmFound._id ){
-                return Promise.reject('Another qcm already has the same question');
+            if(qrlFound && qrl._id !== qrlFound._id ){
+                return Promise.reject('Another qrl already has the same question');
             }
             const res = await this.qrlModel.replaceOne(filterQuery, updatedQrl);
             if (res.matchedCount === 0) {
@@ -108,7 +104,6 @@ export class QrlService {
 
     async getQrlQuestion(question: string): Promise<string> {
         const filterQuery = { question: question };
-        // Only get the choices and not any of the other fields
         try {
             const res = await this.qrlModel.findOne(filterQuery, {question: 1});
             return res.question;
@@ -121,6 +116,6 @@ export class QrlService {
         return this.validatePoints(qrl.points);
     }
     private validatePoints(points: number): boolean {
-        return points % 10 === 0 && points >= 10 && points <= 100;
+        return points % 10 === 0 && points >= QRL_MIN_POINTS && points <= QRL_MAX_POINTS;
     }
 }
-- 
GitLab


From ddb2073e575b993ea1d262fbcf797cb8f5a0d0dd Mon Sep 17 00:00:00 2001
From: Caaf14 <cata-araya@outlook.com>
Date: Sun, 21 Jan 2024 22:37:02 -0500
Subject: [PATCH 3/7] remove forgotten magic numbers

---
 server/app/model/dto/qcm/qcm.dto.constants.ts | 5 ++++-
 server/app/model/dto/qrl/qrl.dto.constants.ts | 3 ++-
 server/app/services/qcm/qcm.service.ts        | 6 +++---
 server/app/services/qrl/qrl.service.ts        | 4 ++--
 4 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/server/app/model/dto/qcm/qcm.dto.constants.ts b/server/app/model/dto/qcm/qcm.dto.constants.ts
index b0968f76..e9922869 100644
--- a/server/app/model/dto/qcm/qcm.dto.constants.ts
+++ b/server/app/model/dto/qcm/qcm.dto.constants.ts
@@ -1,4 +1,7 @@
 export const QCM_MAX_POINTS = 100;
 export const QCM_MIN_POINTS = 10;
 export const QCM_MAX_CHOICES = 4;
-export const QCM_MIN_CHOICES = 2;
\ No newline at end of file
+export const QCM_MIN_CHOICES = 2;
+export const QCM_POINT_DIV_FACTOR = 10;
+export const QCM_MIN_CORRECT_ANSWER = 1;
+export const QCM_MIN_WRONG_ANSWER = 1;
\ No newline at end of file
diff --git a/server/app/model/dto/qrl/qrl.dto.constants.ts b/server/app/model/dto/qrl/qrl.dto.constants.ts
index c7c1155a..c153a3f0 100644
--- a/server/app/model/dto/qrl/qrl.dto.constants.ts
+++ b/server/app/model/dto/qrl/qrl.dto.constants.ts
@@ -1,2 +1,3 @@
 export const QRL_MAX_POINTS = 100;
-export const QRL_MIN_POINTS = 10;
\ No newline at end of file
+export const QRL_MIN_POINTS = 10;
+export const QRL_POINT_DIV_FACTOR = 10;
\ No newline at end of file
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
index 0c0cef09..769538e7 100644
--- a/server/app/services/qcm/qcm.service.ts
+++ b/server/app/services/qcm/qcm.service.ts
@@ -4,7 +4,7 @@ import { Model } from 'mongoose';
 
 import { Choice, Qcm, QcmDocument } from '@app/model/database/qcm';
 import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
-import { QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
+import { QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_CORRECT_ANSWER, QCM_MIN_POINTS, QCM_MIN_WRONG_ANSWER, QCM_POINT_DIV_FACTOR } from '@app/model/dto/qcm/qcm.dto.constants';
 import { UpdateQcmDto } from '@app/model/dto/qcm/update-qcm.dto';
 import { DateService } from '@app/services/date/date.service';
 
@@ -133,7 +133,7 @@ export class QcmService {
         return this.validatePoints(qcm.points) && this.validateChoices(qcm.choices);
     }
     private validatePoints(points: number): boolean {
-        return points % 10 === 0 && points >= QCM_MIN_POINTS && points <= QCM_MAX_POINTS;
+        return points % QCM_POINT_DIV_FACTOR === 0 && points >= QCM_MIN_POINTS && points <= QCM_MAX_POINTS;
     }
     private validateChoices(choices: Choice[]): boolean {
         let correctChoices = 0;
@@ -146,6 +146,6 @@ export class QcmService {
             }
         });
         let sumChoices = correctChoices + incorrectChoices;
-        return (correctChoices >= 1 && incorrectChoices >= 1) && (sumChoices == QCM_MIN_CHOICES || sumChoices == QCM_MAX_POINTS);
+        return (correctChoices >= QCM_MIN_CORRECT_ANSWER && incorrectChoices >= QCM_MIN_WRONG_ANSWER) && (sumChoices == QCM_MIN_CHOICES || sumChoices == QCM_MAX_POINTS);
     }
 }
diff --git a/server/app/services/qrl/qrl.service.ts b/server/app/services/qrl/qrl.service.ts
index 541a0339..64c4b8de 100644
--- a/server/app/services/qrl/qrl.service.ts
+++ b/server/app/services/qrl/qrl.service.ts
@@ -4,7 +4,7 @@ import { Model } from 'mongoose';
 
 import { Qrl, QrlDocument } from '@app/model/database/qrl';
 import { CreateQrlDto } from '@app/model/dto/qrl/create-qrl.dto';
-import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
+import { QRL_MAX_POINTS, QRL_MIN_POINTS, QRL_POINT_DIV_FACTOR } from '@app/model/dto/qrl/qrl.dto.constants';
 import { UpdateQrlDto } from '@app/model/dto/qrl/update-qrl.dto';
 import { DateService } from '@app/services/date/date.service';
 
@@ -116,6 +116,6 @@ export class QrlService {
         return this.validatePoints(qrl.points);
     }
     private validatePoints(points: number): boolean {
-        return points % 10 === 0 && points >= QRL_MIN_POINTS && points <= QRL_MAX_POINTS;
+        return points % QRL_POINT_DIV_FACTOR === 0 && points >= QRL_MIN_POINTS && points <= QRL_MAX_POINTS;
     }
 }
-- 
GitLab


From edd018448e5c1bfbba044813d0a7c9e94e141aa7 Mon Sep 17 00:00:00 2001
From: Caaf14 <cata-araya@outlook.com>
Date: Sun, 21 Jan 2024 23:24:36 -0500
Subject: [PATCH 4/7] fix lint

---
 server/app/model/database/choice.ts           |  4 +
 server/app/model/database/qcm.ts              |  6 +-
 server/app/model/dto/qcm/create-qcm.dto.ts    |  4 +-
 server/app/model/dto/qcm/update-qcm.dto.ts    |  4 +-
 server/app/model/dto/qrl/create-qrl.dto.ts    |  2 +-
 server/app/model/dto/qrl/qrl.dto.constants.ts |  2 +-
 server/app/services/qcm/qcm.service.spec.ts   | 79 +++++++++++++------
 server/app/services/qcm/qcm.service.ts        | 39 +++++----
 server/app/services/qrl/qrl.service.spec.ts   |  6 +-
 server/app/services/qrl/qrl.service.ts        | 10 +--
 10 files changed, 101 insertions(+), 55 deletions(-)
 create mode 100644 server/app/model/database/choice.ts

diff --git a/server/app/model/database/choice.ts b/server/app/model/database/choice.ts
new file mode 100644
index 00000000..9f357da8
--- /dev/null
+++ b/server/app/model/database/choice.ts
@@ -0,0 +1,4 @@
+export class Choice {
+    choice: string;
+    correct: boolean;
+}
\ No newline at end of file
diff --git a/server/app/model/database/qcm.ts b/server/app/model/database/qcm.ts
index 9af8c336..72dcec33 100644
--- a/server/app/model/database/qcm.ts
+++ b/server/app/model/database/qcm.ts
@@ -1,14 +1,10 @@
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 import { ApiProperty } from '@nestjs/swagger';
 import { Document } from 'mongoose';
+import { Choice } from './choice';
 
 export type QcmDocument = Qcm & Document;
 
-export class Choice {
-    choice: string;
-    correct: boolean;
-  }
-
 @Schema()
 export class Qcm {
     @ApiProperty()
diff --git a/server/app/model/dto/qcm/create-qcm.dto.ts b/server/app/model/dto/qcm/create-qcm.dto.ts
index d39f4ae8..a389632f 100644
--- a/server/app/model/dto/qcm/create-qcm.dto.ts
+++ b/server/app/model/dto/qcm/create-qcm.dto.ts
@@ -1,4 +1,4 @@
-import { Choice } from '@app/model/database/qcm';
+import { Choice } from '@app/model/database/choice';
 import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
 import { ApiProperty } from '@nestjs/swagger';
 import { Type } from 'class-transformer';
@@ -26,5 +26,5 @@ export class CreateQcmDto {
     @ApiProperty()
     @IsString()
     lastModified?: string;
-    
+
 }
diff --git a/server/app/model/dto/qcm/update-qcm.dto.ts b/server/app/model/dto/qcm/update-qcm.dto.ts
index d494e1b0..86de8b56 100644
--- a/server/app/model/dto/qcm/update-qcm.dto.ts
+++ b/server/app/model/dto/qcm/update-qcm.dto.ts
@@ -1,4 +1,4 @@
-import { Choice } from '@app/model/database/qcm';
+import { Choice } from '@app/model/database/choice';
 import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
 import { ApiProperty } from '@nestjs/swagger';
 import { Type } from 'class-transformer';
@@ -30,5 +30,5 @@ export class UpdateQcmDto {
     @IsNotEmpty()
     @IsString()
     _id?: string;
-    
+
 }
diff --git a/server/app/model/dto/qrl/create-qrl.dto.ts b/server/app/model/dto/qrl/create-qrl.dto.ts
index 87cc4d40..ae00a276 100644
--- a/server/app/model/dto/qrl/create-qrl.dto.ts
+++ b/server/app/model/dto/qrl/create-qrl.dto.ts
@@ -16,5 +16,5 @@ export class CreateQrlDto {
     @ApiProperty()
     @IsString()
     lastModified: string;
-    
+ 
 }
diff --git a/server/app/model/dto/qrl/qrl.dto.constants.ts b/server/app/model/dto/qrl/qrl.dto.constants.ts
index c153a3f0..5a240dc0 100644
--- a/server/app/model/dto/qrl/qrl.dto.constants.ts
+++ b/server/app/model/dto/qrl/qrl.dto.constants.ts
@@ -1,3 +1,3 @@
 export const QRL_MAX_POINTS = 100;
 export const QRL_MIN_POINTS = 10;
-export const QRL_POINT_DIV_FACTOR = 10;
\ No newline at end of file
+export const QRL_POINT_DIV_FACTOR = 10;
diff --git a/server/app/services/qcm/qcm.service.spec.ts b/server/app/services/qcm/qcm.service.spec.ts
index ee615586..3ceb16e5 100644
--- a/server/app/services/qcm/qcm.service.spec.ts
+++ b/server/app/services/qcm/qcm.service.spec.ts
@@ -208,7 +208,6 @@ describe('QcmServiceEndToEnd', () => {
     it('addQcm() should fail if question already is in DB', async () => {
         await qcmModel.deleteMany({});
         const qcm = getFakeQcm();
-
         await qcmModel.create(qcm);
         await expect(service.addQcm(qcm)).rejects.toBeTruthy();
         expect(await qcmModel.countDocuments()).toEqual(1);
@@ -221,28 +220,64 @@ describe('QcmServiceEndToEnd', () => {
     });
 
     it('addQcm() should fail if the qcm is not a valid', async () => {
+        await qcmModel.deleteMany({});
         const qcm = getFakeQcm();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 35,choices: [
-            { choice: 'Oracles', correct: true },
-            { choice: 'Oracle', correct: false },
-            ] })).rejects.toBeTruthy();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 0,choices: [
-            { choice: 'Oracles', correct: true },
-            { choice: 'Oracle', correct: false },
-            ] })).rejects.toBeTruthy();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 110,choices: [
-            { choice: 'Oracles', correct: true },
-            { choice: 'Oracle', correct: false },
-            ] })).rejects.toBeTruthy();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50,choices: [
-            { choice: 'Oracles', correct: true },
-            { choice: 'Oracle', correct: true },
-            ] })).rejects.toBeTruthy();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50,choices: [
-            { choice: 'Oracles', correct: true },
-            { choice: 'Oracle', correct: false },
-            { choice: 'Oracle', correct: false },
-            ] })).rejects.toBeTruthy();     
+        await expect(
+            service.addQcm({
+                ...qcm,
+                question: 'Question1',
+                points: 35,
+                choices: [
+                    { choice: 'Oracles', correct: true },
+                    { choice: 'Oracle', correct: false },
+                ]
+            })
+        ).rejects.toBeTruthy();
+        await expect(
+            service.addQcm({
+                ...qcm,
+                question: 'Question1',
+                points: 0,
+                choices: [
+                    { choice: 'Oracles', correct: true },
+                    { choice: 'Oracle', correct: false },
+                ]
+            })
+        ).rejects.toBeTruthy();
+        await expect(
+            service.addQcm({
+                ...qcm,
+                question: 'Question1',
+                points: 110,
+                choices: [
+                    { choice: 'Oracles', correct: true },
+                    { choice: 'Oracle', correct: false },
+                ]
+            })
+        ).rejects.toBeTruthy();
+        await expect(
+            service.addQcm({
+                ...qcm,
+                question: 'Question1',
+                points: 50,
+                choices: [
+                    { choice: 'Oracles', correct: true },
+                    { choice: 'Oracle', correct: true },
+                ]
+            })
+        ).rejects.toBeTruthy();
+        await expect(
+            service.addQcm({
+                ...qcm,
+                question: 'Question1',
+                points: 50,
+                choices: [
+                    { choice: 'Oracles', correct: true },
+                    { choice: 'Oracle', correct: false },
+                    { choice: 'Oracle', correct: false },
+                ]
+            })
+        ).rejects.toBeTruthy();    
     });
 });
 
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
index 769538e7..4d1931dc 100644
--- a/server/app/services/qcm/qcm.service.ts
+++ b/server/app/services/qcm/qcm.service.ts
@@ -2,9 +2,17 @@ import { Injectable, Logger } from '@nestjs/common';
 import { InjectModel } from '@nestjs/mongoose';
 import { Model } from 'mongoose';
 
-import { Choice, Qcm, QcmDocument } from '@app/model/database/qcm';
+import { Choice } from '@app/model/database/choice';
+import { Qcm, QcmDocument } from '@app/model/database/qcm';
 import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
-import { QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_CORRECT_ANSWER, QCM_MIN_POINTS, QCM_MIN_WRONG_ANSWER, QCM_POINT_DIV_FACTOR } from '@app/model/dto/qcm/qcm.dto.constants';
+import {
+    QCM_MAX_POINTS,
+    QCM_MIN_CHOICES,
+    QCM_MIN_CORRECT_ANSWER,
+    QCM_MIN_POINTS,
+    QCM_MIN_WRONG_ANSWER,
+    QCM_POINT_DIV_FACTOR
+} from '@app/model/dto/qcm/qcm.dto.constants';
 import { UpdateQcmDto } from '@app/model/dto/qcm/update-qcm.dto';
 import { DateService } from '@app/services/date/date.service';
 
@@ -61,12 +69,11 @@ export class QcmService {
     }
 
     async addQcm(qcm: CreateQcmDto): Promise<void> {
-
         if (!this.validateQcm(qcm)) {
             return Promise.reject('Invalid qcm');
         }
         const existingQcm = await this.getQcmByQuestion(qcm.question);
-        if(existingQcm){
+        if (existingQcm) {
             return Promise.reject('Qcm already exists');
         }
         const currentDate = this.dateService.currentTime();
@@ -94,11 +101,11 @@ export class QcmService {
         const filterQuery = { id: qcm._id };
         const updatedQcm = { ...qcm, lastModified: currentDate };
         try {
-            const qcmFound = await  this.getQcmByQuestion(qcm.question);
-            if(!qcmFound){
+            const qcmFound = await this.getQcmByQuestion(qcm.question);
+            if (!qcmFound) {
                 return Promise.reject('Qcm does not exist');
-            }    
-            if(qcmFound && qcm._id !== qcmFound._id ){
+            }
+            if (qcmFound && qcm._id !== qcmFound._id) {
                 return Promise.reject('Another qcm already has the same question');
             }
             const res = await this.qcmModel.replaceOne(filterQuery, updatedQcm);
@@ -113,16 +120,16 @@ export class QcmService {
     async getQcmQuestion(question: string): Promise<string> {
         const filterQuery = { question: question };
         try {
-            const res = await this.qcmModel.findOne(filterQuery, {question: 1});
+            const res = await this.qcmModel.findOne(filterQuery, { question: 1 });
             return res.question;
         } catch (error) {
             return Promise.reject(`Failed to get data: ${error}`);
         }
     }
-    async getQcmChoices(question: string): Promise<Array<Choice>> {
+    async getQcmChoices(question: string): Promise<Choice[]> {
         const filterQuery = { question: question };
         try {
-            const res = await this.qcmModel.findOne(filterQuery, {choices: 1});
+            const res = await this.qcmModel.findOne(filterQuery, { choices: 1 });
             return res.choices;
         } catch (error) {
             return Promise.reject(`Failed to get data: ${error}`);
@@ -138,14 +145,18 @@ export class QcmService {
     private validateChoices(choices: Choice[]): boolean {
         let correctChoices = 0;
         let incorrectChoices = 0;
-        choices.forEach(choice => {
+        choices.forEach((choice) => {
             if (choice.correct) {
                 correctChoices++;
             } else {
                 incorrectChoices++;
             }
         });
-        let sumChoices = correctChoices + incorrectChoices;
-        return (correctChoices >= QCM_MIN_CORRECT_ANSWER && incorrectChoices >= QCM_MIN_WRONG_ANSWER) && (sumChoices == QCM_MIN_CHOICES || sumChoices == QCM_MAX_POINTS);
+        const sumChoices = correctChoices + incorrectChoices;
+        return (
+            correctChoices >= QCM_MIN_CORRECT_ANSWER &&
+            incorrectChoices >= QCM_MIN_WRONG_ANSWER &&
+            (sumChoices === QCM_MIN_CHOICES || sumChoices === QCM_MAX_POINTS)
+        );
     }
 }
diff --git a/server/app/services/qrl/qrl.service.spec.ts b/server/app/services/qrl/qrl.service.spec.ts
index 236b6b1d..78ce7edd 100644
--- a/server/app/services/qrl/qrl.service.spec.ts
+++ b/server/app/services/qrl/qrl.service.spec.ts
@@ -209,9 +209,9 @@ describe('QrlServiceEndToEnd', () => {
 
     it('addQrl() should fail if the qrl is not a valid', async () => {
         const qrl = getFakeQrl();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 35})).rejects.toBeTruthy();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 0})).rejects.toBeTruthy();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 110})).rejects.toBeTruthy();  
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 35 })).rejects.toBeTruthy();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 0 })).rejects.toBeTruthy();
+        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 110 })).rejects.toBeTruthy();  
     });
 });
 
diff --git a/server/app/services/qrl/qrl.service.ts b/server/app/services/qrl/qrl.service.ts
index 64c4b8de..7fcdb99a 100644
--- a/server/app/services/qrl/qrl.service.ts
+++ b/server/app/services/qrl/qrl.service.ts
@@ -58,7 +58,7 @@ export class QrlService {
             return Promise.reject('Invalid qrl');
         }
         const existingQrl = await this.getQrlByQuestion(qrl.question);
-        if(existingQrl){
+        if (existingQrl) {
             return Promise.reject('Qrl already exists');
         }
         const currentDate = this.dateService.currentTime();
@@ -86,11 +86,11 @@ export class QrlService {
         const filterQuery = { question: qrl.question };
         const updatedQrl = { ...qrl, lastModified: currentDate };
         try {
-            const qrlFound = await  this.getQrlByQuestion(qrl.question);
-            if(!qrlFound){
+            const qrlFound = await this.getQrlByQuestion(qrl.question);
+            if (!qrlFound) {
                 return Promise.reject('Qrl does not exist');
             }    
-            if(qrlFound && qrl._id !== qrlFound._id ){
+            if (qrlFound && qrl._id !== qrlFound._id) {
                 return Promise.reject('Another qrl already has the same question');
             }
             const res = await this.qrlModel.replaceOne(filterQuery, updatedQrl);
@@ -105,7 +105,7 @@ export class QrlService {
     async getQrlQuestion(question: string): Promise<string> {
         const filterQuery = { question: question };
         try {
-            const res = await this.qrlModel.findOne(filterQuery, {question: 1});
+            const res = await this.qrlModel.findOne(filterQuery, { question: 1 });
             return res.question;
         } catch (error) {
             return Promise.reject(`Failed to get data: ${error}`);
-- 
GitLab


From 898a197939204b60e662ae441fbe62bd1ec54c3b Mon Sep 17 00:00:00 2001
From: Caaf14 <cata-araya@outlook.com>
Date: Mon, 22 Jan 2024 18:35:09 -0500
Subject: [PATCH 5/7] rename class choice to choices

---
 server/app/model/database/{choice.ts => choices.ts} | 2 +-
 server/app/model/database/qcm.ts                    | 4 ++--
 server/app/model/dto/qcm/create-qcm.dto.ts          | 8 ++++----
 server/app/model/dto/qcm/update-qcm.dto.ts          | 8 ++++----
 server/app/services/qcm/qcm.service.ts              | 6 +++---
 5 files changed, 14 insertions(+), 14 deletions(-)
 rename server/app/model/database/{choice.ts => choices.ts} (65%)

diff --git a/server/app/model/database/choice.ts b/server/app/model/database/choices.ts
similarity index 65%
rename from server/app/model/database/choice.ts
rename to server/app/model/database/choices.ts
index 9f357da8..d5e2bf3e 100644
--- a/server/app/model/database/choice.ts
+++ b/server/app/model/database/choices.ts
@@ -1,4 +1,4 @@
-export class Choice {
+export class Choices {
     choice: string;
     correct: boolean;
 }
\ No newline at end of file
diff --git a/server/app/model/database/qcm.ts b/server/app/model/database/qcm.ts
index 72dcec33..1c57fc55 100644
--- a/server/app/model/database/qcm.ts
+++ b/server/app/model/database/qcm.ts
@@ -1,7 +1,7 @@
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 import { ApiProperty } from '@nestjs/swagger';
 import { Document } from 'mongoose';
-import { Choice } from './choice';
+import { Choices } from './choices';
 
 export type QcmDocument = Qcm & Document;
 
@@ -17,7 +17,7 @@ export class Qcm {
 
     @ApiProperty()
     @Prop({ required: true })
-    choices: Choice[];
+    choices: Choices[];
 
     @ApiProperty()
     @Prop({ required: true })
diff --git a/server/app/model/dto/qcm/create-qcm.dto.ts b/server/app/model/dto/qcm/create-qcm.dto.ts
index a389632f..6282adfa 100644
--- a/server/app/model/dto/qcm/create-qcm.dto.ts
+++ b/server/app/model/dto/qcm/create-qcm.dto.ts
@@ -1,4 +1,4 @@
-import { Choice } from '@app/model/database/choice';
+import { Choices } from '@app/model/database/choices';
 import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
 import { ApiProperty } from '@nestjs/swagger';
 import { Type } from 'class-transformer';
@@ -15,13 +15,13 @@ export class CreateQcmDto {
     @Max(QCM_MAX_POINTS)
     points: number;
 
-    @ApiProperty({ type: () => Choice, isArray: true })
+    @ApiProperty({ type: () => Choices, isArray: true })
     @IsArray()
     @ArrayMinSize(QCM_MIN_CHOICES)
     @ArrayMaxSize(QCM_MAX_CHOICES)
     @ValidateNested({ each: true })
-    @Type(() => Choice)
-    choices: Choice[];
+    @Type(() => Choices)
+    choices: Choices[];
 
     @ApiProperty()
     @IsString()
diff --git a/server/app/model/dto/qcm/update-qcm.dto.ts b/server/app/model/dto/qcm/update-qcm.dto.ts
index 86de8b56..eec9a56a 100644
--- a/server/app/model/dto/qcm/update-qcm.dto.ts
+++ b/server/app/model/dto/qcm/update-qcm.dto.ts
@@ -1,4 +1,4 @@
-import { Choice } from '@app/model/database/choice';
+import { Choices } from '@app/model/database/choices';
 import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
 import { ApiProperty } from '@nestjs/swagger';
 import { Type } from 'class-transformer';
@@ -17,14 +17,14 @@ export class UpdateQcmDto {
     @Max(QCM_MAX_POINTS)
     points?: number;
 
-    @ApiProperty({ type: () => Choice, isArray: true, required: false })
+    @ApiProperty({ type: () => Choices, isArray: true, required: false })
     @IsOptional()
     @IsArray()
     @ArrayMinSize(QCM_MIN_CHOICES)
     @ArrayMaxSize(QCM_MAX_CHOICES)
     @ValidateNested({ each: true })
-    @Type(() => Choice)
-    choices?: Choice[];
+    @Type(() => Choices)
+    choices?: Choices[];
 
     @ApiProperty()
     @IsNotEmpty()
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
index 4d1931dc..e0ce3f43 100644
--- a/server/app/services/qcm/qcm.service.ts
+++ b/server/app/services/qcm/qcm.service.ts
@@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
 import { InjectModel } from '@nestjs/mongoose';
 import { Model } from 'mongoose';
 
-import { Choice } from '@app/model/database/choice';
+import { Choices } from '@app/model/database/choices';
 import { Qcm, QcmDocument } from '@app/model/database/qcm';
 import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
 import {
@@ -126,7 +126,7 @@ export class QcmService {
             return Promise.reject(`Failed to get data: ${error}`);
         }
     }
-    async getQcmChoices(question: string): Promise<Choice[]> {
+    async getQcmChoices(question: string): Promise<Choices[]> {
         const filterQuery = { question: question };
         try {
             const res = await this.qcmModel.findOne(filterQuery, { choices: 1 });
@@ -142,7 +142,7 @@ export class QcmService {
     private validatePoints(points: number): boolean {
         return points % QCM_POINT_DIV_FACTOR === 0 && points >= QCM_MIN_POINTS && points <= QCM_MAX_POINTS;
     }
-    private validateChoices(choices: Choice[]): boolean {
+    private validateChoices(choices: Choices[]): boolean {
         let correctChoices = 0;
         let incorrectChoices = 0;
         choices.forEach((choice) => {
-- 
GitLab


From 4c6482e8711d07c86c076c54a3ec93c583e40f90 Mon Sep 17 00:00:00 2001
From: Laurent Bourgon <laurent.bourgon@polymtl.ca>
Date: Thu, 25 Jan 2024 15:09:30 -0500
Subject: [PATCH 6/7] add game and its dto

---
 common/game.ts                                |  12 +
 common/question.ts                            |  34 ++
 server/app/app.module.ts                      |   2 +
 .../dto/abstract-question.dto.constants.ts    |   2 +
 server/app/game/dto/abstract-question.dto.ts  |  23 ++
 server/app/game/dto/choice.dto.ts             |  15 +
 server/app/game/dto/game.dto.constants.ts     |   3 +
 server/app/game/dto/game.dto.ts               |  55 ++++
 server/app/game/dto/qcm.dto.constants.ts      |   2 +
 server/app/game/dto/qcm.dto.ts                |  21 ++
 server/app/game/dto/qrl.dto.ts                |  10 +
 server/app/game/game.module.ts                |  15 +
 .../game/schemas/abstract-question.schema.ts  |  16 +
 server/app/game/schemas/game.schema.ts        |  28 ++
 server/app/game/schemas/qcm.schema.ts         |  16 +
 server/app/game/schemas/qrl.schema.ts         |  13 +
 server/app/model/dto/qcm/create-qcm.dto.ts    |  30 --
 server/app/model/dto/qcm/qcm.dto.constants.ts |   7 -
 server/app/model/dto/qcm/update-qcm.dto.ts    |  34 --
 server/app/model/dto/qrl/create-qrl.dto.ts    |  20 --
 server/app/model/dto/qrl/qrl.dto.constants.ts |   3 -
 server/app/model/dto/qrl/update-qrl.dto.ts    |  26 --
 server/app/services/qcm/qcm.service.spec.ts   | 304 ------------------
 server/app/services/qcm/qcm.service.ts        | 162 ----------
 server/app/services/qrl/qrl.service.spec.ts   | 230 -------------
 server/app/services/qrl/qrl.service.ts        | 121 -------
 26 files changed, 267 insertions(+), 937 deletions(-)
 create mode 100644 common/game.ts
 create mode 100644 common/question.ts
 create mode 100644 server/app/game/dto/abstract-question.dto.constants.ts
 create mode 100644 server/app/game/dto/abstract-question.dto.ts
 create mode 100644 server/app/game/dto/choice.dto.ts
 create mode 100644 server/app/game/dto/game.dto.constants.ts
 create mode 100644 server/app/game/dto/game.dto.ts
 create mode 100644 server/app/game/dto/qcm.dto.constants.ts
 create mode 100644 server/app/game/dto/qcm.dto.ts
 create mode 100644 server/app/game/dto/qrl.dto.ts
 create mode 100644 server/app/game/game.module.ts
 create mode 100644 server/app/game/schemas/abstract-question.schema.ts
 create mode 100644 server/app/game/schemas/game.schema.ts
 create mode 100644 server/app/game/schemas/qcm.schema.ts
 create mode 100644 server/app/game/schemas/qrl.schema.ts
 delete mode 100644 server/app/model/dto/qcm/create-qcm.dto.ts
 delete mode 100644 server/app/model/dto/qcm/qcm.dto.constants.ts
 delete mode 100644 server/app/model/dto/qcm/update-qcm.dto.ts
 delete mode 100644 server/app/model/dto/qrl/create-qrl.dto.ts
 delete mode 100644 server/app/model/dto/qrl/qrl.dto.constants.ts
 delete mode 100644 server/app/model/dto/qrl/update-qrl.dto.ts
 delete mode 100644 server/app/services/qcm/qcm.service.spec.ts
 delete mode 100644 server/app/services/qcm/qcm.service.ts
 delete mode 100644 server/app/services/qrl/qrl.service.spec.ts
 delete mode 100644 server/app/services/qrl/qrl.service.ts

diff --git a/common/game.ts b/common/game.ts
new file mode 100644
index 00000000..a5a9db5e
--- /dev/null
+++ b/common/game.ts
@@ -0,0 +1,12 @@
+import { Qcm, Qrl } from './question';
+
+interface BaseGame {
+    title: string;
+    description: string;
+    duration: number;
+    lastModification: Date;
+    questions: (Qcm | Qrl)[];
+}
+
+export type CreateGame = BaseGame;
+export type Game = CreateGame & { _id: string };
diff --git a/common/question.ts b/common/question.ts
new file mode 100644
index 00000000..52fa59f6
--- /dev/null
+++ b/common/question.ts
@@ -0,0 +1,34 @@
+export enum QuestionType {
+    Qcm = 'QCM',
+    Qrl = 'QRL',
+}
+
+interface AbstractQuestion {
+    text: string;
+    points: number;
+}
+
+export interface Choice {
+    text: string;
+    isCorrect: boolean;
+}
+
+interface BaseQcm extends AbstractQuestion {
+    type: QuestionType.Qcm;
+    choices: Choice[];
+}
+
+interface BaseQrl extends AbstractQuestion {
+    type: QuestionType.Qrl;
+}
+
+export type BaseQuestion = BaseQcm | BaseQrl;
+
+export type CreateQcm = BaseQcm;
+export type CreateQrl = BaseQrl;
+export type CreateQuestion = CreateQcm | CreateQrl;
+
+type ConcreteQuestion<T extends BaseQuestion> = T & { _id: string };
+export type Qcm = ConcreteQuestion<CreateQcm>;
+export type Qrl = ConcreteQuestion<CreateQrl>;
+export type Question = ConcreteQuestion<CreateQuestion>;
diff --git a/server/app/app.module.ts b/server/app/app.module.ts
index c48fe474..4af2541a 100644
--- a/server/app/app.module.ts
+++ b/server/app/app.module.ts
@@ -11,11 +11,13 @@ import { ExampleController } from '@app/controllers/example/example.controller';
 import { AuthModule } from '@app/auth/auth.module';
 import { ConfigModule } from '@app/config/config.module';
 import { ConfigService } from '@app/config/config.service';
+import { GameModule } from './game/game.module';
 
 @Module({
     imports: [
         AuthModule,
         ConfigModule,
+        GameModule,
         MongooseModule.forRootAsync({
             inject: [ConfigService],
             useFactory: async (config: ConfigService) => ({
diff --git a/server/app/game/dto/abstract-question.dto.constants.ts b/server/app/game/dto/abstract-question.dto.constants.ts
new file mode 100644
index 00000000..aa590516
--- /dev/null
+++ b/server/app/game/dto/abstract-question.dto.constants.ts
@@ -0,0 +1,2 @@
+export const MIN_POINTS = 10;
+export const MAX_POINTS = 100;
diff --git a/server/app/game/dto/abstract-question.dto.ts b/server/app/game/dto/abstract-question.dto.ts
new file mode 100644
index 00000000..00cfafcf
--- /dev/null
+++ b/server/app/game/dto/abstract-question.dto.ts
@@ -0,0 +1,23 @@
+import { QuestionType } from '@common/question';
+import { IsDefined, IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
+import { MAX_POINTS, MIN_POINTS } from './abstract-question.dto.constants';
+import { ApiProperty } from '@nestjs/swagger';
+
+export abstract class AbstractQuestionDto {
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsEnum(QuestionType)
+    type: QuestionType;
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    text: string;
+
+    @ApiProperty()
+    @IsDefined()
+    @IsInt()
+    @Min(MIN_POINTS)
+    @Max(MAX_POINTS)
+    points: number;
+}
diff --git a/server/app/game/dto/choice.dto.ts b/server/app/game/dto/choice.dto.ts
new file mode 100644
index 00000000..2d909c96
--- /dev/null
+++ b/server/app/game/dto/choice.dto.ts
@@ -0,0 +1,15 @@
+import { Choice } from '@common/question';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
+
+export class ChoiceDto implements Choice {
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    text: string;
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsBoolean()
+    isCorrect: boolean;
+}
diff --git a/server/app/game/dto/game.dto.constants.ts b/server/app/game/dto/game.dto.constants.ts
new file mode 100644
index 00000000..45b54580
--- /dev/null
+++ b/server/app/game/dto/game.dto.constants.ts
@@ -0,0 +1,3 @@
+export const MIN_DURATION = 10;
+export const MAX_DURATION = 60;
+export const MIN_QUESTIONS = 1;
diff --git a/server/app/game/dto/game.dto.ts b/server/app/game/dto/game.dto.ts
new file mode 100644
index 00000000..59aaf0a4
--- /dev/null
+++ b/server/app/game/dto/game.dto.ts
@@ -0,0 +1,55 @@
+import { ArrayMinSize, IsArray, IsDateString, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNested } from 'class-validator';
+import { QcmDto } from './qcm.dto';
+import { Type } from 'class-transformer';
+import { QrlDto } from './qrl.dto';
+import { QuestionType } from '@common/question';
+import { MAX_DURATION, MIN_DURATION, MIN_QUESTIONS } from './game.dto.constants';
+import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
+import { AbstractQuestionDto } from './abstract-question.dto';
+
+@ApiExtraModels(QcmDto, QrlDto)
+export class GameDto {
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    title: string;
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsString()
+    description: string;
+
+    @ApiProperty({ minimum: MIN_DURATION, maximum: MAX_DURATION })
+    @Min(MIN_DURATION)
+    @Max(MAX_DURATION)
+    @IsNumber()
+    duration: number;
+
+    @ApiProperty()
+    @IsNotEmpty()
+    @IsDateString()
+    lastModification: Date;
+
+    @ApiProperty({
+        isArray: true,
+        type: () => AbstractQuestionDto,
+        oneOf: [
+            { type: 'array', items: { $ref: getSchemaPath(QrlDto) } },
+            { type: 'array', items: { $ref: getSchemaPath(QcmDto) } },
+        ],
+    })
+    @ValidateNested({ each: true })
+    @Type(() => AbstractQuestionDto, {
+        keepDiscriminatorProperty: true,
+        discriminator: {
+            property: 'type',
+            subTypes: [
+                { value: QcmDto, name: QuestionType.Qcm },
+                { value: QrlDto, name: QuestionType.Qrl },
+            ],
+        },
+    })
+    @ArrayMinSize(MIN_QUESTIONS)
+    @IsArray()
+    questions: (QcmDto | QrlDto)[];
+}
diff --git a/server/app/game/dto/qcm.dto.constants.ts b/server/app/game/dto/qcm.dto.constants.ts
new file mode 100644
index 00000000..7eea075a
--- /dev/null
+++ b/server/app/game/dto/qcm.dto.constants.ts
@@ -0,0 +1,2 @@
+export const MIN_CHOICES = 2;
+export const MAX_CHOICES = 4;
diff --git a/server/app/game/dto/qcm.dto.ts b/server/app/game/dto/qcm.dto.ts
new file mode 100644
index 00000000..a1049dd8
--- /dev/null
+++ b/server/app/game/dto/qcm.dto.ts
@@ -0,0 +1,21 @@
+import { QuestionType } from '@common/question';
+import { AbstractQuestionDto } from './abstract-question.dto';
+import { ArrayMaxSize, ArrayMinSize, Equals, IsArray, ValidateNested } from 'class-validator';
+import { MAX_CHOICES, MIN_CHOICES } from './qcm.dto.constants';
+import { ChoiceDto } from './choice.dto';
+import { Type } from 'class-transformer';
+import { ApiProperty } from '@nestjs/swagger';
+
+export class QcmDto extends AbstractQuestionDto {
+    @ApiProperty()
+    @Equals(QuestionType.Qcm)
+    type: QuestionType.Qcm;
+
+    @ApiProperty({ isArray: true, type: () => ChoiceDto })
+    @ValidateNested({ each: true })
+    @Type(() => ChoiceDto)
+    @ArrayMaxSize(MAX_CHOICES)
+    @ArrayMinSize(MIN_CHOICES)
+    @IsArray()
+    choices: ChoiceDto[];
+}
diff --git a/server/app/game/dto/qrl.dto.ts b/server/app/game/dto/qrl.dto.ts
new file mode 100644
index 00000000..c5739aab
--- /dev/null
+++ b/server/app/game/dto/qrl.dto.ts
@@ -0,0 +1,10 @@
+import { QuestionType } from '@common/question';
+import { AbstractQuestionDto } from './abstract-question.dto';
+import { Equals } from 'class-validator';
+import { ApiProperty } from '@nestjs/swagger';
+
+export class QrlDto extends AbstractQuestionDto {
+    @ApiProperty()
+    @Equals(QuestionType.Qrl)
+    type: QuestionType.Qrl;
+}
diff --git a/server/app/game/game.module.ts b/server/app/game/game.module.ts
new file mode 100644
index 00000000..2e932d91
--- /dev/null
+++ b/server/app/game/game.module.ts
@@ -0,0 +1,15 @@
+import { Module } from '@nestjs/common';
+import { MongooseModule } from '@nestjs/mongoose';
+import { Game, gameSchema } from './schemas/game.schema';
+
+@Module({
+    imports: [
+        MongooseModule.forFeature([
+            {
+                name: Game.name,
+                schema: gameSchema,
+            },
+        ]),
+    ],
+})
+export class GameModule {}
diff --git a/server/app/game/schemas/abstract-question.schema.ts b/server/app/game/schemas/abstract-question.schema.ts
new file mode 100644
index 00000000..e6475dee
--- /dev/null
+++ b/server/app/game/schemas/abstract-question.schema.ts
@@ -0,0 +1,16 @@
+import { Prop, Schema } from '@nestjs/mongoose';
+import { QuestionType } from '@common/question';
+
+@Schema()
+export abstract class AbstractQuestion {
+    @Prop({ required: true })
+    type: QuestionType;
+
+    @Prop({ required: true })
+    text: string;
+
+    @Prop({ required: true })
+    points: number;
+
+    _id: string;
+}
diff --git a/server/app/game/schemas/game.schema.ts b/server/app/game/schemas/game.schema.ts
new file mode 100644
index 00000000..aedcecb4
--- /dev/null
+++ b/server/app/game/schemas/game.schema.ts
@@ -0,0 +1,28 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { HydratedDocument } from 'mongoose';
+import { Qcm } from './qcm.schema';
+import { Qrl } from './qrl.schema';
+
+@Schema()
+export class Game {
+    @Prop({ required: true })
+    title: string;
+
+    @Prop({ required: true })
+    description: string;
+
+    @Prop({ required: true })
+    duration: number;
+
+    @Prop({ required: true })
+    lastModification: Date;
+
+    @Prop({ required: true })
+    questions: (Qcm | Qrl)[];
+
+    _id: string;
+}
+
+export type GameDocument = HydratedDocument<Game>;
+
+export const gameSchema = SchemaFactory.createForClass(Game);
diff --git a/server/app/game/schemas/qcm.schema.ts b/server/app/game/schemas/qcm.schema.ts
new file mode 100644
index 00000000..90332270
--- /dev/null
+++ b/server/app/game/schemas/qcm.schema.ts
@@ -0,0 +1,16 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { Choice, Qcm as IQcm, QuestionType } from '@common/question';
+import { AbstractQuestion } from './abstract-question.schema';
+import { HydratedDocument } from 'mongoose';
+
+@Schema()
+export class Qcm extends AbstractQuestion implements IQcm {
+    @Prop({ required: true })
+    choices: Choice[];
+
+    type: QuestionType.Qcm;
+}
+
+export type QcmDocument = HydratedDocument<Qcm>;
+
+export const qcmSchema = SchemaFactory.createForClass(Qcm);
diff --git a/server/app/game/schemas/qrl.schema.ts b/server/app/game/schemas/qrl.schema.ts
new file mode 100644
index 00000000..33420505
--- /dev/null
+++ b/server/app/game/schemas/qrl.schema.ts
@@ -0,0 +1,13 @@
+import { Schema, SchemaFactory } from '@nestjs/mongoose';
+import { Qrl as IQrl, QuestionType } from '@common/question';
+import { AbstractQuestion } from './abstract-question.schema';
+import { HydratedDocument } from 'mongoose';
+
+@Schema()
+export class Qrl extends AbstractQuestion implements IQrl {
+    type: QuestionType.Qrl;
+}
+
+export type QrlDocument = HydratedDocument<Qrl>;
+
+export const qrlSchema = SchemaFactory.createForClass(Qrl);
diff --git a/server/app/model/dto/qcm/create-qcm.dto.ts b/server/app/model/dto/qcm/create-qcm.dto.ts
deleted file mode 100644
index 6282adfa..00000000
--- a/server/app/model/dto/qcm/create-qcm.dto.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Choices } from '@app/model/database/choices';
-import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
-import { ApiProperty } from '@nestjs/swagger';
-import { Type } from 'class-transformer';
-import { ArrayMaxSize, ArrayMinSize, IsArray, IsInt, IsString, Max, Min, ValidateNested } from 'class-validator';
-
-export class CreateQcmDto {
-    @ApiProperty()
-    @IsString()
-    question: string;
-
-    @ApiProperty()
-    @IsInt()
-    @Min(QCM_MIN_POINTS)
-    @Max(QCM_MAX_POINTS)
-    points: number;
-
-    @ApiProperty({ type: () => Choices, isArray: true })
-    @IsArray()
-    @ArrayMinSize(QCM_MIN_CHOICES)
-    @ArrayMaxSize(QCM_MAX_CHOICES)
-    @ValidateNested({ each: true })
-    @Type(() => Choices)
-    choices: Choices[];
-
-    @ApiProperty()
-    @IsString()
-    lastModified?: string;
-
-}
diff --git a/server/app/model/dto/qcm/qcm.dto.constants.ts b/server/app/model/dto/qcm/qcm.dto.constants.ts
deleted file mode 100644
index e9922869..00000000
--- a/server/app/model/dto/qcm/qcm.dto.constants.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const QCM_MAX_POINTS = 100;
-export const QCM_MIN_POINTS = 10;
-export const QCM_MAX_CHOICES = 4;
-export const QCM_MIN_CHOICES = 2;
-export const QCM_POINT_DIV_FACTOR = 10;
-export const QCM_MIN_CORRECT_ANSWER = 1;
-export const QCM_MIN_WRONG_ANSWER = 1;
\ No newline at end of file
diff --git a/server/app/model/dto/qcm/update-qcm.dto.ts b/server/app/model/dto/qcm/update-qcm.dto.ts
deleted file mode 100644
index eec9a56a..00000000
--- a/server/app/model/dto/qcm/update-qcm.dto.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Choices } from '@app/model/database/choices';
-import { QCM_MAX_CHOICES, QCM_MAX_POINTS, QCM_MIN_CHOICES, QCM_MIN_POINTS } from '@app/model/dto/qcm/qcm.dto.constants';
-import { ApiProperty } from '@nestjs/swagger';
-import { Type } from 'class-transformer';
-import { ArrayMaxSize, ArrayMinSize, IsArray, IsInt, IsNotEmpty, IsOptional, IsString, Max, Min, ValidateNested } from 'class-validator';
-
-export class UpdateQcmDto {
-    @ApiProperty({ required: false })
-    @IsOptional()
-    @IsString()
-    question?: string;
-
-    @ApiProperty({ required: false })
-    @IsOptional()
-    @IsInt()
-    @Min(QCM_MIN_POINTS)
-    @Max(QCM_MAX_POINTS)
-    points?: number;
-
-    @ApiProperty({ type: () => Choices, isArray: true, required: false })
-    @IsOptional()
-    @IsArray()
-    @ArrayMinSize(QCM_MIN_CHOICES)
-    @ArrayMaxSize(QCM_MAX_CHOICES)
-    @ValidateNested({ each: true })
-    @Type(() => Choices)
-    choices?: Choices[];
-
-    @ApiProperty()
-    @IsNotEmpty()
-    @IsString()
-    _id?: string;
-
-}
diff --git a/server/app/model/dto/qrl/create-qrl.dto.ts b/server/app/model/dto/qrl/create-qrl.dto.ts
deleted file mode 100644
index ae00a276..00000000
--- a/server/app/model/dto/qrl/create-qrl.dto.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
-import { ApiProperty } from '@nestjs/swagger';
-import { IsInt, IsString, Max, Min } from 'class-validator';
-
-export class CreateQrlDto {
-    @ApiProperty()
-    @IsString()
-    question: string;
-
-    @ApiProperty()
-    @IsInt()
-    @Min(QRL_MIN_POINTS)
-    @Max(QRL_MAX_POINTS)
-    points: number;
-
-    @ApiProperty()
-    @IsString()
-    lastModified: string;
- 
-}
diff --git a/server/app/model/dto/qrl/qrl.dto.constants.ts b/server/app/model/dto/qrl/qrl.dto.constants.ts
deleted file mode 100644
index 5a240dc0..00000000
--- a/server/app/model/dto/qrl/qrl.dto.constants.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const QRL_MAX_POINTS = 100;
-export const QRL_MIN_POINTS = 10;
-export const QRL_POINT_DIV_FACTOR = 10;
diff --git a/server/app/model/dto/qrl/update-qrl.dto.ts b/server/app/model/dto/qrl/update-qrl.dto.ts
deleted file mode 100644
index a0ba07e3..00000000
--- a/server/app/model/dto/qrl/update-qrl.dto.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { QRL_MAX_POINTS, QRL_MIN_POINTS } from '@app/model/dto/qrl/qrl.dto.constants';
-import { ApiProperty } from '@nestjs/swagger';
-import { IsInt, IsNotEmpty, IsOptional, IsString, Max, Min } from 'class-validator';
-
-export class UpdateQrlDto {
-    @ApiProperty({ required: false })
-    @IsOptional()
-    @IsString()
-    question?: string;
-
-    @ApiProperty({ required: false })
-    @IsOptional()
-    @IsInt()
-    @Min(QRL_MIN_POINTS)
-    @Max(QRL_MAX_POINTS)
-    points?: number;
-
-    @ApiProperty()
-    @IsString()
-    lastModified: string;
-
-    @ApiProperty()
-    @IsNotEmpty()
-    @IsString()
-    _id?: string;
-}
diff --git a/server/app/services/qcm/qcm.service.spec.ts b/server/app/services/qcm/qcm.service.spec.ts
deleted file mode 100644
index 3ceb16e5..00000000
--- a/server/app/services/qcm/qcm.service.spec.ts
+++ /dev/null
@@ -1,304 +0,0 @@
-import { DateService } from '@app/services/date/date.service';
-import { Logger } from '@nestjs/common';
-import { Test, TestingModule } from '@nestjs/testing';
-import { MongoMemoryServer } from 'mongodb-memory-server';
-import { Connection, Model } from 'mongoose';
-import { QcmService } from './qcm.service';
-
-import { Qcm, QcmDocument, qcmSchema } from '@app/model/database/qcm';
-import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
-
-describe('QcmService', () => {
-    let service: QcmService;
-    let qcmModel: Model<QcmDocument>;
-
-    beforeEach(async () => {
-
-        qcmModel = {
-            countDocuments: jest.fn(),
-            insertMany: jest.fn(),
-            create: jest.fn(),
-            find: jest.fn(),
-            findOne: jest.fn(),
-            deleteOne: jest.fn(),
-            update: jest.fn(),
-            updateOne: jest.fn(),
-        } as unknown as Model<QcmDocument>;
-
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [
-                QcmService,
-                Logger,
-                DateService,
-                {
-                    provide: getModelToken(Qcm.name),
-                    useValue: qcmModel,
-                },
-            ],
-        }).compile();
-
-        service = module.get<QcmService>(QcmService);
-    });
-
-    it('should be defined', () => {
-        expect(service).toBeDefined();
-    });
-
-    it('database should be populated when there is no data', async () => {
-        jest.spyOn(qcmModel, 'countDocuments').mockResolvedValue(0);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await service.start();
-        expect(spyPopulateDB).toHaveBeenCalled();
-    });
-
-    it('database should not be populated when there is some data', async () => {
-        jest.spyOn(qcmModel, 'countDocuments').mockResolvedValue(1);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await service.start();
-        expect(spyPopulateDB).not.toHaveBeenCalled();
-    });
-});
-
-const DELAY_BEFORE_CLOSING_CONNECTION = 200;
-
-describe('QcmServiceEndToEnd', () => {
-    let service: QcmService;
-    let qcmModel: Model<QcmDocument>;
-    let mongoServer: MongoMemoryServer;
-    let connection: Connection;
-
-    beforeEach(async () => {
-        mongoServer = await MongoMemoryServer.create();
-        const module = await Test.createTestingModule({
-            imports: [
-                MongooseModule.forRootAsync({
-                    useFactory: () => ({
-                        uri: mongoServer.getUri(),
-                    }),
-                }),
-                MongooseModule.forFeature([{ name: Qcm.name, schema: qcmSchema }]),
-            ],
-            providers: [QcmService, Logger, DateService],
-        }).compile();
-
-        service = module.get<QcmService>(QcmService);
-        qcmModel = module.get<Model<QcmDocument>>(getModelToken(Qcm.name));
-        connection = await module.get(getConnectionToken());
-    });
-
-    afterEach((done) => {
-        setTimeout(async () => {
-            await connection.close();
-            await mongoServer.stop();
-            done();
-        }, DELAY_BEFORE_CLOSING_CONNECTION);
-    });
-
-    it('should be defined', () => {
-        expect(service).toBeDefined();
-        expect(qcmModel).toBeDefined();
-    });
-
-    it('start() should populate the database when there is no data', async () => {
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await qcmModel.deleteMany({});
-        await service.start();
-        expect(spyPopulateDB).toHaveBeenCalled();
-    });
-
-    it('start() should not populate the DB when there is some data', async () => {
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        expect(spyPopulateDB).not.toHaveBeenCalled();
-    });
-
-    it('populateDB() should add 2 new qcms', async () => {
-        const eltCountsBefore = await qcmModel.countDocuments();
-        await service.populateDB();
-        const eltCountsAfter = await qcmModel.countDocuments();
-        expect(eltCountsAfter - eltCountsBefore).toEqual(2);
-    });
-
-    it('getAllQcms() return all qcms in database', async () => {
-        await qcmModel.deleteMany({});
-        expect((await service.getAllQcm()).length).toEqual(0);
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        expect((await service.getAllQcm()).length).toEqual(1);
-    });
-
-    it('getQcmByQuestion() return qcm with the specified question', async () => {
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        expect(await service.getQcmByQuestion(qcm.question)).toEqual(expect.objectContaining(qcm));
-    });
-
-    it('getQcmQuestion() should return qcm question', async () => {
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        const question = await service.getQcmQuestion(qcm.question);
-        expect(question).toEqual(qcm.question);
-    });
-
-    it('getQcmQuestion() should fail if qcm does not exist', async () => {
-        const qcm = getFakeQcm();
-        await expect(service.getQcmQuestion(qcm.question)).rejects.toBeTruthy();
-    });
-
-    it('getQcmChoices() should return qcm question', async () => {
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        const choices = await service.getQcmChoices(qcm.question);
-        expect(choices).toEqual(qcm.choices);
-    });
-
-    it('getQcmChoices() should fail if qcm does not exist', async () => {
-        const qcm = getFakeQcm();
-        await expect(service.getQcmChoices(qcm.question)).rejects.toBeTruthy();
-    });
-
-    it('modifyQcm() should fail if a qcm with same question exists', async () => {
-        const qcm = getFakeQcm();
-        const qcm2 = getFakeQcm2();
-        await qcmModel.create(qcm);
-        await qcmModel.create(qcm2);
-        qcm.question = qcm2.question;
-        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
-    });
-
-    it('modifyQcm() should fail if qcm does not exist', async () => {
-        const qcm = getFakeQcm();
-        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
-    });
-
-    it('modifyQcm() should fail if mongo query failed', async () => {
-        jest.spyOn(qcmModel, 'updateOne').mockRejectedValue('');
-        const qcm = getFakeQcm();
-        await expect(service.modifyQcm(qcm)).rejects.toBeTruthy();
-    });
-
-    it('deleteQcm() should delete the qcm', async () => {
-        await qcmModel.deleteMany({});
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        await service.deleteQcm(qcm.question);
-        expect(await qcmModel.countDocuments()).toEqual(0);
-    });
-
-    it('deleteQcm() should fail if the qcm does not exist', async () => {
-        await qcmModel.deleteMany({});
-        const qcm = getFakeQcm();
-        await expect(service.deleteQcm(qcm.question)).rejects.toBeTruthy();
-    });
-
-    it('deleteQcm() should fail if mongo query failed', async () => {
-        jest.spyOn(qcmModel, 'deleteOne').mockRejectedValue('');
-        const qcm = getFakeQcm();
-        await expect(service.deleteQcm(qcm.question)).rejects.toBeTruthy();
-    });
-
-    it('addQcm() should add the qcm to the DB', async () => {
-        await qcmModel.deleteMany({});
-        const qcm = getFakeQcm();
-        await service.addQcm({ ...qcm, question: 'Question1', points: 50 });
-        expect(await qcmModel.countDocuments()).toEqual(1);
-    });
-
-    it('addQcm() should fail if question already is in DB', async () => {
-        await qcmModel.deleteMany({});
-        const qcm = getFakeQcm();
-        await qcmModel.create(qcm);
-        await expect(service.addQcm(qcm)).rejects.toBeTruthy();
-        expect(await qcmModel.countDocuments()).toEqual(1);
-    });
-
-    it('addQcm() should fail if mongo query failed', async () => {
-        jest.spyOn(qcmModel, 'create').mockImplementation(async () => Promise.reject(''));
-        const qcm = getFakeQcm();
-        await expect(service.addQcm({ ...qcm, question: 'Question1', points: 50 })).rejects.toBeTruthy();
-    });
-
-    it('addQcm() should fail if the qcm is not a valid', async () => {
-        await qcmModel.deleteMany({});
-        const qcm = getFakeQcm();
-        await expect(
-            service.addQcm({
-                ...qcm,
-                question: 'Question1',
-                points: 35,
-                choices: [
-                    { choice: 'Oracles', correct: true },
-                    { choice: 'Oracle', correct: false },
-                ]
-            })
-        ).rejects.toBeTruthy();
-        await expect(
-            service.addQcm({
-                ...qcm,
-                question: 'Question1',
-                points: 0,
-                choices: [
-                    { choice: 'Oracles', correct: true },
-                    { choice: 'Oracle', correct: false },
-                ]
-            })
-        ).rejects.toBeTruthy();
-        await expect(
-            service.addQcm({
-                ...qcm,
-                question: 'Question1',
-                points: 110,
-                choices: [
-                    { choice: 'Oracles', correct: true },
-                    { choice: 'Oracle', correct: false },
-                ]
-            })
-        ).rejects.toBeTruthy();
-        await expect(
-            service.addQcm({
-                ...qcm,
-                question: 'Question1',
-                points: 50,
-                choices: [
-                    { choice: 'Oracles', correct: true },
-                    { choice: 'Oracle', correct: true },
-                ]
-            })
-        ).rejects.toBeTruthy();
-        await expect(
-            service.addQcm({
-                ...qcm,
-                question: 'Question1',
-                points: 50,
-                choices: [
-                    { choice: 'Oracles', correct: true },
-                    { choice: 'Oracle', correct: false },
-                    { choice: 'Oracle', correct: false },
-                ]
-            })
-        ).rejects.toBeTruthy();    
-    });
-});
-
-const getFakeQcm = (): Qcm => ({
-    question: 'Quel est le nom de ce jeu?',
-    points: 100,
-    choices: [
-        { choice: 'Oracles', correct: true },
-        { choice: 'Oracle', correct: false },
-    ],
-    lastModified: getRandomString(),
-});
-const getFakeQcm2 = (): Qcm => ({
-    question: 'Quel est le nom de ce jeu2?',
-    points: 100,
-    choices: [
-        { choice: 'Oracles', correct: true },
-        { choice: 'Oracle', correct: false },
-    ],
-    lastModified: getRandomString(),
-});
-
-const BASE_36 = 36;
-const getRandomString = (): string => (Math.random() + 1).toString(BASE_36).substring(2);
diff --git a/server/app/services/qcm/qcm.service.ts b/server/app/services/qcm/qcm.service.ts
deleted file mode 100644
index e0ce3f43..00000000
--- a/server/app/services/qcm/qcm.service.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import { Injectable, Logger } from '@nestjs/common';
-import { InjectModel } from '@nestjs/mongoose';
-import { Model } from 'mongoose';
-
-import { Choices } from '@app/model/database/choices';
-import { Qcm, QcmDocument } from '@app/model/database/qcm';
-import { CreateQcmDto } from '@app/model/dto/qcm/create-qcm.dto';
-import {
-    QCM_MAX_POINTS,
-    QCM_MIN_CHOICES,
-    QCM_MIN_CORRECT_ANSWER,
-    QCM_MIN_POINTS,
-    QCM_MIN_WRONG_ANSWER,
-    QCM_POINT_DIV_FACTOR
-} from '@app/model/dto/qcm/qcm.dto.constants';
-import { UpdateQcmDto } from '@app/model/dto/qcm/update-qcm.dto';
-import { DateService } from '@app/services/date/date.service';
-
-@Injectable()
-export class QcmService {
-    constructor(
-        @InjectModel(Qcm.name) public qcmModel: Model<QcmDocument>,
-        private readonly logger: Logger,
-        private readonly dateService: DateService,
-    ) {
-        this.start();
-    }
-
-    async start() {
-        if ((await this.qcmModel.countDocuments()) === 0) {
-            await this.populateDB();
-        }
-    }
-
-    async populateDB(): Promise<void> {
-        const qcmQuestions: CreateQcmDto[] = [
-            {
-                question: 'Dans quelle ville est poly?',
-                points: 10,
-                choices: [
-                    { choice: 'Montreal', correct: true },
-                    { choice: 'Quebec', correct: false },
-                    { choice: 'Sherbrooke', correct: false },
-                    { choice: 'Toronto', correct: false },
-                ],
-                lastModified: this.dateService.currentTime(),
-            },
-            {
-                question: 'Combien de membres dans cette équipe de projet 2?',
-                points: 100,
-                choices: [
-                    { choice: '6', correct: true },
-                    { choice: '5', correct: false },
-                ],
-                lastModified: this.dateService.currentTime(),
-            },
-        ];
-
-        this.logger.log('THIS ADDS DATA TO THE DATABASE, DO NOT USE OTHERWISE');
-        await this.qcmModel.insertMany(qcmQuestions);
-    }
-
-    async getAllQcm(): Promise<Qcm[]> {
-        return await this.qcmModel.find({});
-    }
-
-    async getQcmByQuestion(question: string): Promise<Qcm> {
-        return await this.qcmModel.findOne({ question: question });
-    }
-
-    async addQcm(qcm: CreateQcmDto): Promise<void> {
-        if (!this.validateQcm(qcm)) {
-            return Promise.reject('Invalid qcm');
-        }
-        const existingQcm = await this.getQcmByQuestion(qcm.question);
-        if (existingQcm) {
-            return Promise.reject('Qcm already exists');
-        }
-        const currentDate = this.dateService.currentTime();
-        const newQcm = { ...qcm, lastModified: currentDate };
-        try {
-            await this.qcmModel.create(newQcm);
-        } catch (error) {
-            return Promise.reject(`Failed to insert course: ${error}`);
-        }
-    }
-
-    async deleteQcm(question: string): Promise<void> {
-        try {
-            const res = await this.qcmModel.deleteOne({ question: question });
-            if (res.deletedCount === 0) {
-                return Promise.reject('Could not find course');
-            }
-        } catch (error) {
-            return Promise.reject(`Failed to delete course: ${error}`);
-        }
-    }
-
-    async modifyQcm(qcm: UpdateQcmDto): Promise<void> {
-        const currentDate = this.dateService.currentTime();
-        const filterQuery = { id: qcm._id };
-        const updatedQcm = { ...qcm, lastModified: currentDate };
-        try {
-            const qcmFound = await this.getQcmByQuestion(qcm.question);
-            if (!qcmFound) {
-                return Promise.reject('Qcm does not exist');
-            }
-            if (qcmFound && qcm._id !== qcmFound._id) {
-                return Promise.reject('Another qcm already has the same question');
-            }
-            const res = await this.qcmModel.replaceOne(filterQuery, updatedQcm);
-            if (res.matchedCount === 0) {
-                return Promise.reject('Could not find qcm');
-            }
-        } catch (error) {
-            return Promise.reject(`Failed to update qcm: ${error}`);
-        }
-    }
-
-    async getQcmQuestion(question: string): Promise<string> {
-        const filterQuery = { question: question };
-        try {
-            const res = await this.qcmModel.findOne(filterQuery, { question: 1 });
-            return res.question;
-        } catch (error) {
-            return Promise.reject(`Failed to get data: ${error}`);
-        }
-    }
-    async getQcmChoices(question: string): Promise<Choices[]> {
-        const filterQuery = { question: question };
-        try {
-            const res = await this.qcmModel.findOne(filterQuery, { choices: 1 });
-            return res.choices;
-        } catch (error) {
-            return Promise.reject(`Failed to get data: ${error}`);
-        }
-    }
-
-    private validateQcm(qcm: CreateQcmDto): boolean {
-        return this.validatePoints(qcm.points) && this.validateChoices(qcm.choices);
-    }
-    private validatePoints(points: number): boolean {
-        return points % QCM_POINT_DIV_FACTOR === 0 && points >= QCM_MIN_POINTS && points <= QCM_MAX_POINTS;
-    }
-    private validateChoices(choices: Choices[]): boolean {
-        let correctChoices = 0;
-        let incorrectChoices = 0;
-        choices.forEach((choice) => {
-            if (choice.correct) {
-                correctChoices++;
-            } else {
-                incorrectChoices++;
-            }
-        });
-        const sumChoices = correctChoices + incorrectChoices;
-        return (
-            correctChoices >= QCM_MIN_CORRECT_ANSWER &&
-            incorrectChoices >= QCM_MIN_WRONG_ANSWER &&
-            (sumChoices === QCM_MIN_CHOICES || sumChoices === QCM_MAX_POINTS)
-        );
-    }
-}
diff --git a/server/app/services/qrl/qrl.service.spec.ts b/server/app/services/qrl/qrl.service.spec.ts
deleted file mode 100644
index 78ce7edd..00000000
--- a/server/app/services/qrl/qrl.service.spec.ts
+++ /dev/null
@@ -1,230 +0,0 @@
-import { DateService } from '@app/services/date/date.service';
-import { Logger } from '@nestjs/common';
-import { Test, TestingModule } from '@nestjs/testing';
-import { MongoMemoryServer } from 'mongodb-memory-server';
-import { Connection, Model } from 'mongoose';
-import { QrlService } from './qrl.service';
-
-import { Qrl, QrlDocument, qrlSchema } from '@app/model/database/qrl';
-import { MongooseModule, getConnectionToken, getModelToken } from '@nestjs/mongoose';
-
-describe('QrlService', () => {
-    let service: QrlService;
-    let qrlModel: Model<QrlDocument>;
-
-    beforeEach(async () => {
-
-        qrlModel = {
-            countDocuments: jest.fn(),
-            insertMany: jest.fn(),
-            create: jest.fn(),
-            find: jest.fn(),
-            findOne: jest.fn(),
-            deleteOne: jest.fn(),
-            update: jest.fn(),
-            updateOne: jest.fn(),
-        } as unknown as Model<QrlDocument>;
-
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [
-                QrlService,
-                Logger,
-                DateService,
-                {
-                    provide: getModelToken(Qrl.name),
-                    useValue: qrlModel,
-                },
-            ],
-        }).compile();
-
-        service = module.get<QrlService>(QrlService);
-    });
-
-    it('should be defined', () => {
-        expect(service).toBeDefined();
-    });
-
-    it('database should be populated when there is no data', async () => {
-        jest.spyOn(qrlModel, 'countDocuments').mockResolvedValue(0);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await service.start();
-        expect(spyPopulateDB).toHaveBeenCalled();
-    });
-
-    it('database should not be populated when there is some data', async () => {
-        jest.spyOn(qrlModel, 'countDocuments').mockResolvedValue(1);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await service.start();
-        expect(spyPopulateDB).not.toHaveBeenCalled();
-    });
-});
-
-const DELAY_BEFORE_CLOSING_CONNECTION = 200;
-
-describe('QrlServiceEndToEnd', () => {
-    let service: QrlService;
-    let qrlModel: Model<QrlDocument>;
-    let mongoServer: MongoMemoryServer;
-    let connection: Connection;
-
-    beforeEach(async () => {
-        mongoServer = await MongoMemoryServer.create();
-        const module = await Test.createTestingModule({
-            imports: [
-                MongooseModule.forRootAsync({
-                    useFactory: () => ({
-                        uri: mongoServer.getUri(),
-                    }),
-                }),
-                MongooseModule.forFeature([{ name: Qrl.name, schema: qrlSchema }]),
-            ],
-            providers: [QrlService, Logger, DateService],
-        }).compile();
-
-        service = module.get<QrlService>(QrlService);
-        qrlModel = module.get<Model<QrlDocument>>(getModelToken(Qrl.name));
-        connection = await module.get(getConnectionToken());
-    });
-
-    afterEach((done) => {
-        setTimeout(async () => {
-            await connection.close();
-            await mongoServer.stop();
-            done();
-        }, DELAY_BEFORE_CLOSING_CONNECTION);
-    });
-
-    it('should be defined', () => {
-        expect(service).toBeDefined();
-        expect(qrlModel).toBeDefined();
-    });
-
-    it('start() should populate the database when there is no data', async () => {
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        await qrlModel.deleteMany({});
-        await service.start();
-        expect(spyPopulateDB).toHaveBeenCalled();
-    });
-
-    it('start() should not populate the DB when there is some data', async () => {
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        const spyPopulateDB = jest.spyOn(service, 'populateDB');
-        expect(spyPopulateDB).not.toHaveBeenCalled();
-    });
-
-    it('populateDB() should add 2 new qrls', async () => {
-        const eltCountsBefore = await qrlModel.countDocuments();
-        await service.populateDB();
-        const eltCountsAfter = await qrlModel.countDocuments();
-        expect(eltCountsAfter - eltCountsBefore).toEqual(2);
-    });
-
-    it('getAllQrl() return all qrls in database', async () => {
-        await qrlModel.deleteMany({});
-        expect((await service.getAllQrl()).length).toEqual(0);
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        expect((await service.getAllQrl()).length).toEqual(1);
-    });
-
-    it('getQrlByQuestion() return qrl with the specified question', async () => {
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        expect(await service.getQrlByQuestion(qrl.question)).toEqual(expect.objectContaining(qrl));
-    });
-
-    it('getQrlQuestion() should return qrl question', async () => {
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        const question = await service.getQrlQuestion(qrl.question);
-        expect(question).toEqual(qrl.question);
-    });
-
-    it('getQrlQuestion() should fail if qrl does not exist', async () => {
-        const qrl = getFakeQrl();
-        await expect(service.getQrlQuestion(qrl.question)).rejects.toBeTruthy();
-    });
-
-    it('modifyQrl() should fail if a qrl with same question exists', async () => {
-        const qrl = getFakeQrl();
-        const qrl2 = getFakeQrl2();
-        await qrlModel.create(qrl);
-        await qrlModel.create(qrl2);
-        qrl.question = qrl2.question;
-        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
-    });
-
-    it('modifyQrl() should fail if qrl does not exist', async () => {
-        const qrl = getFakeQrl();
-        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
-    });
-
-    it('modifyQrl() should fail if mongo query failed', async () => {
-        jest.spyOn(qrlModel, 'updateOne').mockRejectedValue('');
-        const qrl = getFakeQrl();
-        await expect(service.modifyQrl(qrl)).rejects.toBeTruthy();
-    });
-
-    it('deleteQrl() should delete the qrl', async () => {
-        await qrlModel.deleteMany({});
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        await service.deleteQrl(qrl.question);
-        expect(await qrlModel.countDocuments()).toEqual(0);
-    });
-
-    it('deleteQrl() should fail if the qrl does not exist', async () => {
-        await qrlModel.deleteMany({});
-        const qrl = getFakeQrl();
-        await expect(service.deleteQrl(qrl.question)).rejects.toBeTruthy();
-    });
-
-    it('deleteQrl() should fail if mongo query failed', async () => {
-        jest.spyOn(qrlModel, 'deleteOne').mockRejectedValue('');
-        const qrl = getFakeQrl();
-        await expect(service.deleteQrl(qrl.question)).rejects.toBeTruthy();
-    });
-
-    it('addQrl() should add the qrl to the DB', async () => {
-        await qrlModel.deleteMany({});
-        const qrl = getFakeQrl();
-        await service.addQrl(qrl);
-        expect(await qrlModel.countDocuments()).toEqual(1);
-    });
-
-    it('addQrl() should fail if question already is in DB', async () => {
-        await qrlModel.deleteMany({});
-        const qrl = getFakeQrl();
-        await qrlModel.create(qrl);
-        await expect(service.addQrl(qrl)).rejects.toBeTruthy();
-        expect(await qrlModel.countDocuments()).toEqual(1);
-    });
-
-    it('addQrl() should fail if mongo query failed', async () => {
-        jest.spyOn(qrlModel, 'create').mockImplementation(async () => Promise.reject(''));
-        const qrl = getFakeQrl();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 50 })).rejects.toBeTruthy();
-    });
-
-    it('addQrl() should fail if the qrl is not a valid', async () => {
-        const qrl = getFakeQrl();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 35 })).rejects.toBeTruthy();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 0 })).rejects.toBeTruthy();
-        await expect(service.addQrl({ ...qrl, question: 'Question1', points: 110 })).rejects.toBeTruthy();  
-    });
-});
-
-const getFakeQrl = (): Qrl => ({
-    question: 'Quel est le nom de ce jeu?',
-    points: 100,
-    lastModified: getRandomString(),
-});
-const getFakeQrl2 = (): Qrl => ({
-    question: 'Quel est le nom de ce jeu2?',
-    points: 100,
-    lastModified: getRandomString(),
-});
-
-const BASE_36 = 36;
-const getRandomString = (): string => (Math.random() + 1).toString(BASE_36).substring(2);
diff --git a/server/app/services/qrl/qrl.service.ts b/server/app/services/qrl/qrl.service.ts
deleted file mode 100644
index 7fcdb99a..00000000
--- a/server/app/services/qrl/qrl.service.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { Injectable, Logger } from '@nestjs/common';
-import { InjectModel } from '@nestjs/mongoose';
-import { Model } from 'mongoose';
-
-import { Qrl, QrlDocument } from '@app/model/database/qrl';
-import { CreateQrlDto } from '@app/model/dto/qrl/create-qrl.dto';
-import { QRL_MAX_POINTS, QRL_MIN_POINTS, QRL_POINT_DIV_FACTOR } from '@app/model/dto/qrl/qrl.dto.constants';
-import { UpdateQrlDto } from '@app/model/dto/qrl/update-qrl.dto';
-import { DateService } from '@app/services/date/date.service';
-
-@Injectable()
-export class QrlService {
-    constructor(
-        @InjectModel(Qrl.name) public qrlModel: Model<QrlDocument>,
-        private readonly logger: Logger,
-        private readonly dateService: DateService,
-    ) {
-        this.start();
-    }
-
-    async start() {
-        if ((await this.qrlModel.countDocuments()) === 0) {
-            await this.populateDB();
-        }
-    }
-
-    async populateDB(): Promise<void> {
-        const qrlQuestions: CreateQrlDto[] = [
-            {
-                question: 'Dans quelle ville est poly?',
-                points: 10,
-                lastModified: this.dateService.currentTime(),
-            },
-            {
-                question: 'Combien de membres dans cette équipe de projet 2?',
-                points: 100,
-                lastModified: this.dateService.currentTime(),
-            },
-        ];
-
-        this.logger.log('THIS ADDS DATA TO THE DATABASE, DO NOT USE OTHERWISE');
-        await this.qrlModel.insertMany(qrlQuestions);
-    }
-
-    async getAllQrl(): Promise<Qrl[]> {
-        return await this.qrlModel.find({});
-    }
-
-    async getQrlById(id: string): Promise<Qrl> {
-        return await this.qrlModel.findById(id);
-    }
-    async getQrlByQuestion(question: string): Promise<Qrl> {
-        return await this.qrlModel.findOne({ question: question });
-    }
-
-    async addQrl(qrl: CreateQrlDto): Promise<void> {
-        if (!this.validateQrl(qrl)) {
-            return Promise.reject('Invalid qrl');
-        }
-        const existingQrl = await this.getQrlByQuestion(qrl.question);
-        if (existingQrl) {
-            return Promise.reject('Qrl already exists');
-        }
-        const currentDate = this.dateService.currentTime();
-        const newQrl = { ...qrl, lastModified: currentDate };
-        try {
-            await this.qrlModel.create(newQrl);
-        } catch (error) {
-            return Promise.reject(`Failed to insert course: ${error}`);
-        }
-    }
-
-    async deleteQrl(question: string): Promise<void> {
-        try {
-            const res = await this.qrlModel.deleteOne({ question: question });
-            if (res.deletedCount === 0) {
-                return Promise.reject('Could not find course');
-            }
-        } catch (error) {
-            return Promise.reject(`Failed to delete course: ${error}`);
-        }
-    }
-
-    async modifyQrl(qrl: UpdateQrlDto): Promise<void> {
-        const currentDate = this.dateService.currentTime();
-        const filterQuery = { question: qrl.question };
-        const updatedQrl = { ...qrl, lastModified: currentDate };
-        try {
-            const qrlFound = await this.getQrlByQuestion(qrl.question);
-            if (!qrlFound) {
-                return Promise.reject('Qrl does not exist');
-            }    
-            if (qrlFound && qrl._id !== qrlFound._id) {
-                return Promise.reject('Another qrl already has the same question');
-            }
-            const res = await this.qrlModel.replaceOne(filterQuery, updatedQrl);
-            if (res.matchedCount === 0) {
-                return Promise.reject('Could not find qrl');
-            }
-        } catch (error) {
-            return Promise.reject(`Failed to update qrl: ${error}`);
-        }
-    }
-
-    async getQrlQuestion(question: string): Promise<string> {
-        const filterQuery = { question: question };
-        try {
-            const res = await this.qrlModel.findOne(filterQuery, { question: 1 });
-            return res.question;
-        } catch (error) {
-            return Promise.reject(`Failed to get data: ${error}`);
-        }
-    }
-
-    private validateQrl(qrl: CreateQrlDto): boolean {
-        return this.validatePoints(qrl.points);
-    }
-    private validatePoints(points: number): boolean {
-        return points % QRL_POINT_DIV_FACTOR === 0 && points >= QRL_MIN_POINTS && points <= QRL_MAX_POINTS;
-    }
-}
-- 
GitLab


From 13bd8898b0b57b5fa50b1d5ccf14946985298a31 Mon Sep 17 00:00:00 2001
From: Laurent Bourgon <laurent.bourgon@polymtl.ca>
Date: Thu, 25 Jan 2024 15:13:27 -0500
Subject: [PATCH 7/7] remove old schemas

---
 server/app/model/database/choices.ts |  4 ----
 server/app/model/database/qcm.ts     | 30 ----------------------------
 server/app/model/database/qrl.ts     | 25 -----------------------
 3 files changed, 59 deletions(-)
 delete mode 100644 server/app/model/database/choices.ts
 delete mode 100644 server/app/model/database/qcm.ts
 delete mode 100644 server/app/model/database/qrl.ts

diff --git a/server/app/model/database/choices.ts b/server/app/model/database/choices.ts
deleted file mode 100644
index d5e2bf3e..00000000
--- a/server/app/model/database/choices.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export class Choices {
-    choice: string;
-    correct: boolean;
-}
\ No newline at end of file
diff --git a/server/app/model/database/qcm.ts b/server/app/model/database/qcm.ts
deleted file mode 100644
index 1c57fc55..00000000
--- a/server/app/model/database/qcm.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
-import { ApiProperty } from '@nestjs/swagger';
-import { Document } from 'mongoose';
-import { Choices } from './choices';
-
-export type QcmDocument = Qcm & Document;
-
-@Schema()
-export class Qcm {
-    @ApiProperty()
-    @Prop({ required: true })
-    question: string;
-
-    @ApiProperty()
-    @Prop({ required: true })
-    points: number;
-
-    @ApiProperty()
-    @Prop({ required: true })
-    choices: Choices[];
-
-    @ApiProperty()
-    @Prop({ required: true })
-    lastModified: string;
-
-    @ApiProperty()
-    _id?: string;
-}
-
-export const qcmSchema = SchemaFactory.createForClass(Qcm);
diff --git a/server/app/model/database/qrl.ts b/server/app/model/database/qrl.ts
deleted file mode 100644
index da2d7c2e..00000000
--- a/server/app/model/database/qrl.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
-import { ApiProperty } from '@nestjs/swagger';
-import { Document } from 'mongoose';
-
-export type QrlDocument = Qrl & Document;
-
-@Schema()
-export class Qrl {
-    @ApiProperty()
-    @Prop({ required: true })
-    question: string;
-
-    @ApiProperty()
-    @Prop({ required: true })
-    points: number;
-
-    @ApiProperty()
-    @Prop({ required: true })
-    lastModified: string;
-
-    @ApiProperty()
-    _id?: string;
-}
-
-export const qrlSchema = SchemaFactory.createForClass(Qrl);
-- 
GitLab