From bcbfda0af01c152e48e7d9f92a79043aa4df4c19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me>
Date: Wed, 13 Sep 2023 20:44:39 +0200
Subject: [PATCH] Rename enonce and exercice to assignment and exercise

---
 ...e_gitlab_ci.yml => exercise_gitlab_ci.yml} |   8 +-
 ExpressAPI/package.json                       |   2 +-
 .../migration.sql                             |  65 ++++++++
 ExpressAPI/prisma/schema.prisma               |  20 +--
 ExpressAPI/src/config/Config.ts               |  57 +++----
 ExpressAPI/src/controllers/Session.ts         |   4 +-
 ExpressAPI/src/helpers/DojoValidators.ts      |   8 +-
 ExpressAPI/src/managers/AssignmentManager.ts  |  39 +++++
 ExpressAPI/src/managers/EnonceManager.ts      |  40 -----
 ...{ExerciceManager.ts => ExerciseManager.ts} |  12 +-
 .../src/middlewares/ParamsCallbackManager.ts  |  30 ++--
 .../src/middlewares/SecurityMiddleware.ts     |  14 +-
 ExpressAPI/src/routes/ApiRoutesManager.ts     |  18 +--
 .../{EnonceRoutes.ts => AssignmentRoutes.ts}  | 122 +++++++--------
 .../{ExerciceRoutes.ts => ExerciseRoutes.ts}  | 147 +++++++-----------
 ExpressAPI/src/routes/GitlabRoutes.ts         |   4 +-
 ExpressAPI/src/shared                         |   2 +-
 ExpressAPI/src/types/DatabaseTypes.ts         |  46 +++---
 ExpressAPI/src/types/SecurityCheckType.ts     |   8 +-
 ExpressAPI/src/types/express/index.d.ts       |   6 +-
 20 files changed, 324 insertions(+), 328 deletions(-)
 rename ExpressAPI/assets/{exercice_gitlab_ci.yml => exercise_gitlab_ci.yml} (85%)
 create mode 100644 ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql
 create mode 100644 ExpressAPI/src/managers/AssignmentManager.ts
 delete mode 100644 ExpressAPI/src/managers/EnonceManager.ts
 rename ExpressAPI/src/managers/{ExerciceManager.ts => ExerciseManager.ts} (57%)
 rename ExpressAPI/src/routes/{EnonceRoutes.ts => AssignmentRoutes.ts} (54%)
 rename ExpressAPI/src/routes/{ExerciceRoutes.ts => ExerciseRoutes.ts} (53%)

diff --git a/ExpressAPI/assets/exercice_gitlab_ci.yml b/ExpressAPI/assets/exercise_gitlab_ci.yml
similarity index 85%
rename from ExpressAPI/assets/exercice_gitlab_ci.yml
rename to ExpressAPI/assets/exercise_gitlab_ci.yml
index 9a9877f..f658fa5 100644
--- a/ExpressAPI/assets/exercice_gitlab_ci.yml
+++ b/ExpressAPI/assets/exercise_gitlab_ci.yml
@@ -1,6 +1,6 @@
 ###################################################################################################################
 # DO NOT MODIFY THIS FILE
-# This file is the ci/cd pipeline that will be used to test your exercice
+# This file is the ci/cd pipeline that will be used to test your exercise
 ###################################################################################################################
 
 variables:
@@ -16,13 +16,13 @@ stages:
 dojo:
     stage: dojo
     tags:
-        - dojo_exercice
+        - dojo_exercise
     services:
         - docker:dind
     image:
-        name: dojohesso/dojo_exercice_checker:latest
+        name: dojohesso/dojo_exercise_checker:latest
     script:
-        - dojo_exercice_checker
+        - dojo_exercise_checker
     artifacts:
         when: always
         paths:
diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json
index 558242b..f15be44 100644
--- a/ExpressAPI/package.json
+++ b/ExpressAPI/package.json
@@ -1,7 +1,7 @@
 {
     "name"           : "dojo_backend_api",
     "description"    : "Backend API for the Dojo Project",
-    "version"        : "1.0.1",
+    "version"        : "2.0.0",
     "license"        : "",
     "author"         : "Michaƫl Minelli <michael-jean.minelli@hesge.ch>",
     "main"           : "app.js",
diff --git a/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql b/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql
new file mode 100644
index 0000000..34fe271
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql
@@ -0,0 +1,65 @@
+-- RenameTable
+RENAME TABLE `Enonce` TO `Assignment`;
+
+-- RenameTable
+RENAME TABLE `Exercice` TO `Exercise`;
+
+-- RenameTable
+RENAME TABLE `_EnonceToUser` TO `_AssignmentToUser`;
+
+-- RenameTable
+RENAME TABLE `_ExerciceToUser` TO `_ExerciseToUser`;
+
+-- AlterTable
+ALTER TABLE `Exercise` RENAME COLUMN `enonceName` TO `assignmentName`;
+
+-- AlterTable
+ALTER TABLE `Result` RENAME COLUMN `exerciceId` TO `exerciseId`;
+
+-- RenameIndex
+ALTER TABLE `Exercise` RENAME INDEX `Exercice_secret_key` TO `Exercise_secret_key`;
+
+-- RenameIndex
+ALTER TABLE `Exercise` RENAME INDEX `Exercice_enonceName_fkey` TO `Exercise_assignmentName_fkey`;
+
+-- RenameIndex
+ALTER TABLE `_ExerciseToUser` RENAME INDEX `_ExerciceToUser_AB_unique` TO `_ExerciseToUser_AB_unique`;
+
+-- RenameIndex
+ALTER TABLE `_ExerciseToUser` RENAME INDEX `_ExerciceToUser_B_index` TO `_ExerciseToUser_B_index`;
+
+-- RenameIndex
+ALTER TABLE `_AssignmentToUser` RENAME INDEX `_EnonceToUser_AB_unique` TO `_AssignmentToUser_AB_unique`;
+
+-- RenameIndex
+ALTER TABLE `_AssignmentToUser` RENAME INDEX `_EnonceToUser_B_index` TO `_AssignmentToUser_B_index`;
+
+-- Rename foreign key
+ALTER TABLE `Exercise`
+    DROP FOREIGN KEY `Exercice_enonceName_fkey`,
+    ADD CONSTRAINT `Exercise_assignmentName_fkey` FOREIGN KEY (`assignmentName`) REFERENCES `Assignment`(`name`) ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- Rename foreign key
+ALTER TABLE `Result`
+    DROP FOREIGN KEY `Result_exerciceId_fkey`,
+    ADD CONSTRAINT `Result_exerciseId_fkey` FOREIGN KEY (`exerciseId`) REFERENCES `Exercise`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Rename foreign key
+ALTER TABLE `_AssignmentToUser`
+    DROP FOREIGN KEY `_EnonceToUser_A_fkey`,
+    ADD CONSTRAINT `_AssignmentToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Assignment`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Rename foreign key
+ALTER TABLE `_AssignmentToUser`
+    DROP FOREIGN KEY `_EnonceToUser_B_fkey`,
+    ADD CONSTRAINT `_AssignmentToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Rename foreign key
+ALTER TABLE `_ExerciseToUser`
+    DROP FOREIGN KEY `_ExerciceToUser_A_fkey`,
+    ADD CONSTRAINT `_ExerciseToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Exercise`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Rename foreign key
+ALTER TABLE `_ExerciseToUser`
+    DROP FOREIGN KEY `_ExerciceToUser_B_fkey`,
+    ADD CONSTRAINT `_ExerciseToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
\ No newline at end of file
diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma
index 7f3f418..df6640e 100644
--- a/ExpressAPI/prisma/schema.prisma
+++ b/ExpressAPI/prisma/schema.prisma
@@ -17,11 +17,11 @@ model User {
     role      String?
     deleted   Boolean @default(false)
 
-    enonces   Enonce[]
-    exercices Exercice[]
+    assignments Assignment[]
+    exercises   Exercise[]
 }
 
-model Enonce {
+model Assignment {
     name               String   @id
     gitlabId           Int
     gitlabLink         String
@@ -30,13 +30,13 @@ model Enonce {
     gitlabLastInfoDate DateTime
     published          Boolean  @default(false)
 
-    exercices Exercice[]
+    exercises Exercise[]
     staff     User[]
 }
 
-model Exercice {
+model Exercise {
     id                 String   @id @db.Char(36)
-    enonceName         String
+    assignmentName     String
     name               String
     secret             String   @unique @db.Char(36)
     gitlabId           Int
@@ -45,14 +45,14 @@ model Exercice {
     gitlabLastInfo     Json     @db.Json
     gitlabLastInfoDate DateTime
 
-    enonce Enonce @relation(fields: [enonceName], references: [name], onDelete: NoAction, onUpdate: Cascade)
+    assignment Assignment @relation(fields: [assignmentName], references: [name], onDelete: NoAction, onUpdate: Cascade)
 
     members User[]
     results Result[]
 }
 
 model Result {
-    exerciceId String   @db.Char(36)
+    exerciseId String   @db.Char(36)
     dateTime   DateTime @default(now())
     success    Boolean
     exitCode   Int
@@ -60,7 +60,7 @@ model Result {
     results    Json     @db.Json
     files      Json     @db.Json
 
-    exercice Exercice @relation(fields: [exerciceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+    exercise Exercise @relation(fields: [exerciseId], references: [id], onDelete: Cascade, onUpdate: Cascade)
 
-    @@id([exerciceId, dateTime])
+    @@id([exerciseId, dateTime])
 }
diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts
index 83edf27..a88b015 100644
--- a/ExpressAPI/src/config/Config.ts
+++ b/ExpressAPI/src/config/Config.ts
@@ -1,7 +1,7 @@
 import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
-import { Exercice }     from '../types/DatabaseTypes';
 import path             from 'path';
 import fs               from 'fs';
+import { Exercise }     from '../types/DatabaseTypes';
 
 
 class Config {
@@ -18,14 +18,14 @@ class Config {
     };
 
     public gitlab: {
-        apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; enonces: number; exercices: number; };
+        apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; };
     };
 
-    public enonce: {
+    public assignment: {
         default: { description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string }; baseFiles: Array<string>; filename: string
     };
 
-    public exercice: {
+    public exercise: {
         maxSameName: number; resultsFolder: string, pipelineResultsFolder: string; default: { description: string; visibility: string; };
     };
 
@@ -38,8 +38,7 @@ class Config {
         };
 
         this.jwtConfig = {
-            secret   : process.env.JWT_SECRET_KEY || '',
-            expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
+            secret: process.env.JWT_SECRET_KEY || '', expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
         };
 
         this.permissions = {
@@ -47,41 +46,23 @@ class Config {
         };
 
         this.gitlab = {
-            apiURL : process.env.GITLAB_API_URL || '',
-            urls   : JSON.parse(process.env.GITLAB_URLS || '[]'),
-            account: {
-                id      : Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0),
-                username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '',
-                token   : process.env.GITLAB_DOJO_ACCOUNT_TOKEN || ''
-            },
-            group  : {
-                root     : Number(process.env.GITLAB_GROUP_ROOT_ID || 0),
-                templates: Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0),
-                enonces  : Number(process.env.GITLAB_GROUP_ENONCES_ID || 0),
-                exercices: Number(process.env.GITLAB_GROUP_EXERCICES_ID || 0)
+            apiURL  : process.env.GITLAB_API_URL || '', urls: JSON.parse(process.env.GITLAB_URLS || '[]'), account: {
+                id: Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '', token: process.env.GITLAB_DOJO_ACCOUNT_TOKEN || ''
+            }, group: {
+                root: Number(process.env.GITLAB_GROUP_ROOT_ID || 0), templates: Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0), assignments: Number(process.env.GITLAB_GROUP_ASSIGNMENTS_ID || 0), exercises: Number(process.env.GITLAB_GROUP_EXERCISES_ID || 0)
             }
         };
 
-        this.enonce = {
-            default  : {
-                description         : process.env.ENONCE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
-                initReadme          : process.env.ENONCE_DEFAULT_INIT_README?.toBoolean() ?? false,
-                sharedRunnersEnabled: process.env.ENONCE_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true,
-                visibility          : process.env.ENONCE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE,
-                wikiEnabled         : process.env.ENONCE_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false,
-                template            : process.env.ENONCE_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? ''
-            },
-            baseFiles: JSON.parse(process.env.ENONCE_BASE_FILES || '[]'),
-            filename : process.env.ENONCE_FILENAME || ''
+        this.assignment = {
+            default     : {
+                description: process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', initReadme: process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false, sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true, visibility: process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE, wikiEnabled: process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false, template: process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? ''
+            }, baseFiles: JSON.parse(process.env.ASSIGNMENT_BASE_FILES || '[]'), filename: process.env.ASSIGNMENT_FILENAME || ''
         };
 
-        this.exercice = {
-            maxSameName          : Number(process.env.EXERCICE_MAX_SAME_NAME || 0),
-            resultsFolder        : process.env.EXERCICE_RESULTS_FOLDER?.convertWithEnvVars() ?? '',
-            pipelineResultsFolder: process.env.EXERCICE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercice creation and muste be interpreted at exercice runtime
-            default              : {
-                description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
-                visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
+        this.exercise = {
+            maxSameName: Number(process.env.EXERCISE_MAX_SAME_NAME || 0), resultsFolder: process.env.EXERCISE_RESULTS_FOLDER?.convertWithEnvVars() ?? '', pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime
+            default    : {
+                description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', visibility: process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
             }
         };
 
@@ -89,8 +70,8 @@ class Config {
         this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10);
     }
 
-    public getResultsFolder(exercice: Exercice): string {
-        const folderPath = path.join(this.exercice.resultsFolder, exercice.enonceName, exercice.id);
+    public getResultsFolder(exercise: Exercise): string {
+        const folderPath = path.join(this.exercise.resultsFolder, exercise.assignmentName, exercise.id);
 
         fs.mkdirSync(folderPath, { recursive: true });
 
diff --git a/ExpressAPI/src/controllers/Session.ts b/ExpressAPI/src/controllers/Session.ts
index 7f71824..fd6b85f 100644
--- a/ExpressAPI/src/controllers/Session.ts
+++ b/ExpressAPI/src/controllers/Session.ts
@@ -4,8 +4,8 @@ import { JwtPayload }                   from 'jsonwebtoken';
 import Config                           from '../config/Config';
 import express                          from 'express';
 import UserManager                      from '../managers/UserManager';
-import DojoResponse                     from '../shared/types/Dojo/DojoResponse';
 import { User }                         from '../types/DatabaseTypes';
+import DojoBackendResponse              from '../shared/types/Dojo/DojoBackendResponse';
 
 
 class Session {
@@ -46,7 +46,7 @@ class Session {
         return profileJson === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {});
     }
 
-    private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoResponse<T>> {
+    private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoBackendResponse<T>> {
         const profileJson = this.profile;
 
         let reasonPhrase = '';
diff --git a/ExpressAPI/src/helpers/DojoValidators.ts b/ExpressAPI/src/helpers/DojoValidators.ts
index c443036..ccb2b65 100644
--- a/ExpressAPI/src/helpers/DojoValidators.ts
+++ b/ExpressAPI/src/helpers/DojoValidators.ts
@@ -4,7 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres
 import { BailOptions, ValidationChain }                             from 'express-validator/src/chain';
 import GitlabManager                                                from '../managers/GitlabManager';
 import express                                                      from 'express';
-import SharedExerciceHelper                                         from '../shared/helpers/Dojo/SharedExerciceHelper';
+import SharedExerciseHelper                                         from '../shared/helpers/Dojo/SharedExerciseHelper';
 
 
 declare type DojoMeta = Meta & {
@@ -75,7 +75,7 @@ class DojoValidators {
                                                                               if ( template ) {
                                                                                   return `${ Config.gitlab.urls[0].replace(/^([a-z]{3,5}:\/{2})?(.*)/, `$1${ Config.gitlab.account.username }:${ Config.gitlab.account.token }@$2`) }${ template }.git`;
                                                                               } else {
-                                                                                  return Config.enonce.default.template;
+                                                                                  return Config.assignment.default.template;
                                                                               }
                                                                           } catch ( e ) { }
 
@@ -83,7 +83,7 @@ class DojoValidators {
                                                                       }
                                                                   });
 
-    readonly exerciceResultsValidator = this.toValidatorSchemaOptions({
+    readonly exerciseResultsValidator = this.toValidatorSchemaOptions({
                                                                           bail        : true,
                                                                           errorMessage: 'Results: not provided or invalid format',
                                                                           options     : (_value, {
@@ -93,7 +93,7 @@ class DojoValidators {
                                                                               return new Promise((resolve, reject) => {
                                                                                   const results = this.getParamValue(req, path);
                                                                                   if ( results ) {
-                                                                                      SharedExerciceHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
+                                                                                      SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
                                                                                   } else {
                                                                                       reject();
                                                                                   }
diff --git a/ExpressAPI/src/managers/AssignmentManager.ts b/ExpressAPI/src/managers/AssignmentManager.ts
new file mode 100644
index 0000000..0f2bc54
--- /dev/null
+++ b/ExpressAPI/src/managers/AssignmentManager.ts
@@ -0,0 +1,39 @@
+import { Prisma }           from '@prisma/client';
+import { Assignment, User } from '../types/DatabaseTypes';
+import db                   from '../helpers/DatabaseHelper';
+
+
+class AssignmentManager {
+    async isUserAllowedToAccessAssignment(assignment: Assignment, user: User): Promise<boolean> {
+        if ( !assignment.staff ) {
+            assignment.staff = await db.assignment.findUnique({
+                                                                  where: {
+                                                                      name: assignment.name
+                                                                  }
+                                                              }).staff() ?? [];
+        }
+        return assignment.staff.findIndex(staff => staff.id === user.id) !== -1;
+    }
+
+    async getByName(name: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> {
+        return await db.assignment.findUnique({
+                                                  where     : {
+                                                      name: name
+                                                  }, include: include
+                                              }) as unknown as Assignment ?? undefined;
+    }
+
+    getByGitlabLink(gitlabLink: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> {
+        const name = gitlabLink.replace('.git', '').split('/').pop()!;
+
+        return this.getByName(name, include);
+    }
+
+    get(nameOrUrl: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> {
+        // We can use the same function for both name and url because the name is the last part of the url and the name extraction from the url doesn't corrupt the name
+        return this.getByGitlabLink(nameOrUrl, include);
+    }
+}
+
+
+export default new AssignmentManager();
diff --git a/ExpressAPI/src/managers/EnonceManager.ts b/ExpressAPI/src/managers/EnonceManager.ts
deleted file mode 100644
index 7c8b612..0000000
--- a/ExpressAPI/src/managers/EnonceManager.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Prisma }       from '@prisma/client';
-import { Enonce, User } from '../types/DatabaseTypes';
-import db               from '../helpers/DatabaseHelper';
-
-
-class EnonceManager {
-    async isUserAllowedToAccessEnonce(enonce: Enonce, user: User): Promise<boolean> {
-        if ( !enonce.staff ) {
-            enonce.staff = await db.enonce.findUnique({
-                                                          where: {
-                                                              name: enonce.name
-                                                          }
-                                                      }).staff() ?? [];
-        }
-        return enonce.staff.findIndex(staff => staff.id === user.id) !== -1;
-    }
-
-    async getByName(name: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> {
-        return await db.enonce.findUnique({
-                                              where  : {
-                                                  name: name
-                                              },
-                                              include: include
-                                          }) as unknown as Enonce ?? undefined;
-    }
-
-    getByGitlabLink(gitlabLink: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> {
-        const name = gitlabLink.replace('.git', '').split('/').pop()!;
-
-        return this.getByName(name, include);
-    }
-
-    get(nameOrUrl: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> {
-        // We can use the same function for both name and url because the name is the last part of the url and the name extraction from the url doesn't corrupt the name
-        return this.getByGitlabLink(nameOrUrl, include);
-    }
-}
-
-
-export default new EnonceManager();
diff --git a/ExpressAPI/src/managers/ExerciceManager.ts b/ExpressAPI/src/managers/ExerciseManager.ts
similarity index 57%
rename from ExpressAPI/src/managers/ExerciceManager.ts
rename to ExpressAPI/src/managers/ExerciseManager.ts
index 698e16f..95bc487 100644
--- a/ExpressAPI/src/managers/ExerciceManager.ts
+++ b/ExpressAPI/src/managers/ExerciseManager.ts
@@ -1,18 +1,18 @@
 import { Prisma }   from '@prisma/client';
-import { Exercice } from '../types/DatabaseTypes';
+import { Exercise } from '../types/DatabaseTypes';
 import db           from '../helpers/DatabaseHelper';
 
 
-class ExerciceManager {
-    async get(id: string, include: Prisma.ExerciceInclude | undefined = undefined): Promise<Exercice | undefined> {
-        return await db.exercice.findUnique({
+class ExerciseManager {
+    async get(id: string, include: Prisma.ExerciseInclude | undefined = undefined): Promise<Exercise | undefined> {
+        return await db.exercise.findUnique({
                                                 where  : {
                                                     id: id
                                                 },
                                                 include: include
-                                            }) as unknown as Exercice ?? undefined;
+                                            }) as unknown as Exercise ?? undefined;
     }
 }
 
 
-export default new ExerciceManager();
+export default new ExerciseManager();
diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
index 64a7bda..e81e172 100644
--- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
+++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
@@ -1,8 +1,8 @@
-import { Express }     from 'express-serve-static-core';
-import express         from 'express';
-import { StatusCodes } from 'http-status-codes';
-import EnonceManager   from '../managers/EnonceManager';
-import ExerciceManager from '../managers/ExerciceManager';
+import { Express }       from 'express-serve-static-core';
+import express           from 'express';
+import { StatusCodes }   from 'http-status-codes';
+import ExerciseManager   from '../managers/ExerciseManager';
+import AssignmentManager from '../managers/AssignmentManager';
 
 
 type GetFunction = (id: string | number, ...args: Array<any>) => Promise<any>
@@ -27,23 +27,23 @@ class ParamsCallbackManager {
     initBoundParams(req: express.Request) {
         if ( !req.boundParams ) {
             req.boundParams = {
-                enonce  : undefined,
-                exercice: undefined
+                assignment: undefined,
+                exercise  : undefined
             };
         }
     }
 
     register(backend: Express) {
-        this.listenParam('enonceNameOrUrl', backend, (EnonceManager.get as GetFunction).bind(EnonceManager), [ {
-            exercices: true,
+        this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ {
+            exercises: true,
             staff    : true
-        } ], 'enonce');
+        } ], 'assignment');
 
-        this.listenParam('exerciceId', backend, (ExerciceManager.get as GetFunction).bind(ExerciceManager), [ {
-            enonce : true,
-            members: true,
-            results: true
-        } ], 'exercice');
+        this.listenParam('exerciseId', backend, (ExerciseManager.get as GetFunction).bind(ExerciseManager), [ {
+            assignment: true,
+            members   : true,
+            results   : true
+        } ], 'exercise');
     }
 }
 
diff --git a/ExpressAPI/src/middlewares/SecurityMiddleware.ts b/ExpressAPI/src/middlewares/SecurityMiddleware.ts
index 9ba9c2f..cc7e067 100644
--- a/ExpressAPI/src/middlewares/SecurityMiddleware.ts
+++ b/ExpressAPI/src/middlewares/SecurityMiddleware.ts
@@ -2,7 +2,7 @@ import express           from 'express';
 import { StatusCodes }   from 'http-status-codes';
 import SecurityCheckType from '../types/SecurityCheckType';
 import logger            from '../shared/logging/WinstonLogger';
-import EnonceManager     from '../managers/EnonceManager';
+import AssignmentManager from '../managers/AssignmentManager';
 
 
 class SecurityMiddleware {
@@ -24,14 +24,14 @@ class SecurityMiddleware {
                             case SecurityCheckType.TEACHING_STAFF:
                                 isAllowed = isAllowed || req.session.profile.isTeachingStaff;
                                 break;
-                            case SecurityCheckType.ENONCE_STAFF:
-                                isAllowed = isAllowed || await EnonceManager.isUserAllowedToAccessEnonce(req.boundParams.enonce!, req.session.profile);
+                            case SecurityCheckType.ASSIGNMENT_STAFF:
+                                isAllowed = isAllowed || await AssignmentManager.isUserAllowedToAccessAssignment(req.boundParams.assignment!, req.session.profile);
                                 break;
-                            case SecurityCheckType.ENONCE_IS_PUBLISHED:
-                                isAllowed = isAllowed || (req.boundParams.enonce?.published ?? false);
+                            case SecurityCheckType.ASSIGNMENT_IS_PUBLISHED:
+                                isAllowed = isAllowed || (req.boundParams.assignment?.published ?? false);
                                 break;
-                            case SecurityCheckType.EXERCICE_SECRET:
-                                isAllowed = isAllowed || req.headers.authorization?.replace('ExerciceSecret ', '') === req.boundParams.exercice!.secret;
+                            case SecurityCheckType.EXERCISE_SECRET:
+                                isAllowed = isAllowed || req.headers.authorization?.replace('ExerciseSecret ', '') === req.boundParams.exercise!.secret;
                                 break;
                             default:
                                 break;
diff --git a/ExpressAPI/src/routes/ApiRoutesManager.ts b/ExpressAPI/src/routes/ApiRoutesManager.ts
index d7641bc..ec4eeb9 100644
--- a/ExpressAPI/src/routes/ApiRoutesManager.ts
+++ b/ExpressAPI/src/routes/ApiRoutesManager.ts
@@ -1,10 +1,10 @@
-import { Express }    from 'express-serve-static-core';
-import RoutesManager  from '../express/RoutesManager';
-import BaseRoutes     from './BaseRoutes';
-import SessionRoutes  from './SessionRoutes';
-import EnonceRoutes   from './EnonceRoutes';
-import GitlabRoutes   from './GitlabRoutes';
-import ExerciceRoutes from './ExerciceRoutes';
+import { Express }      from 'express-serve-static-core';
+import RoutesManager    from '../express/RoutesManager';
+import BaseRoutes       from './BaseRoutes';
+import SessionRoutes    from './SessionRoutes';
+import AssignmentRoutes from './AssignmentRoutes';
+import GitlabRoutes     from './GitlabRoutes';
+import ExerciseRoutes   from './ExerciseRoutes';
 
 
 class AdminRoutesManager implements RoutesManager {
@@ -12,8 +12,8 @@ class AdminRoutesManager implements RoutesManager {
         BaseRoutes.registerOnBackend(backend);
         SessionRoutes.registerOnBackend(backend);
         GitlabRoutes.registerOnBackend(backend);
-        EnonceRoutes.registerOnBackend(backend);
-        ExerciceRoutes.registerOnBackend(backend);
+        AssignmentRoutes.registerOnBackend(backend);
+        ExerciseRoutes.registerOnBackend(backend);
     }
 }
 
diff --git a/ExpressAPI/src/routes/EnonceRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
similarity index 54%
rename from ExpressAPI/src/routes/EnonceRoutes.ts
rename to ExpressAPI/src/routes/AssignmentRoutes.ts
index ddff650..af42e42 100644
--- a/ExpressAPI/src/routes/EnonceRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -17,62 +17,55 @@ import logger                         from '../shared/logging/WinstonLogger';
 import DojoValidators                 from '../helpers/DojoValidators';
 import { Prisma }                     from '@prisma/client';
 import db                             from '../helpers/DatabaseHelper';
-import { Enonce }                     from '../types/DatabaseTypes';
-import EnonceManager                  from '../managers/EnonceManager';
+import { Assignment }                 from '../types/DatabaseTypes';
+import AssignmentManager              from '../managers/AssignmentManager';
 import GitlabVisibility               from '../shared/types/Gitlab/GitlabVisibility';
 
 
-class EnonceRoutes implements RoutesManager {
-    private readonly enonceValidator: ExpressValidator.Schema = {
-        name    : {
-            trim    : true,
-            notEmpty: true
-        },
-        members : {
-            trim           : true,
-            notEmpty       : true,
-            customSanitizer: DojoValidators.jsonSanitizer
-        },
-        template: {
-            trim           : true,
-            custom         : DojoValidators.templateUrlValidator,
-            customSanitizer: DojoValidators.templateUrlSanitizer
+class AssignmentRoutes implements RoutesManager {
+    private readonly assignmentValidator: ExpressValidator.Schema = {
+        name       : {
+            trim: true, notEmpty: true
+        }, members : {
+            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
+        }, template: {
+            trim: true, custom: DojoValidators.templateUrlValidator, customSanitizer: DojoValidators.templateUrlSanitizer
         }
     };
 
     registerOnBackend(backend: Express) {
-        backend.get('/enonces/:enonceNameOrUrl', SecurityMiddleware.check(true), this.getEnonce);
-        backend.post('/enonces', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.enonceValidator), this.createEnonce);
+        backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment);
+        backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment);
 
-        backend.patch('/enonces/:enonceNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(true));
-        backend.patch('/enonces/:enonceNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(false));
+        backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true));
+        backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false));
     }
 
-    // Get an enonce by its name or gitlab url
-    private async getEnonce(req: express.Request, res: express.Response) {
-        const enonce: Enonce | undefined = req.boundParams.enonce;
+    // Get an assignment by its name or gitlab url
+    private async getAssignment(req: express.Request, res: express.Response) {
+        const assignment: Assignment | undefined = req.boundParams.assignment;
 
-        if ( enonce && !enonce.published && !await EnonceManager.isUserAllowedToAccessEnonce(enonce, req.session.profile) ) {
+        if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment, req.session.profile) ) {
             // @ts-ignore
-            delete enonce.gitlabId;
+            delete assignment.gitlabId;
             // @ts-ignore
-            delete enonce.gitlabLink;
+            delete assignment.gitlabLink;
             // @ts-ignore
-            delete enonce.gitlabCreationInfo;
+            delete assignment.gitlabCreationInfo;
             // @ts-ignore
-            delete enonce.gitlabLastInfo;
+            delete assignment.gitlabLastInfo;
             // @ts-ignore
-            delete enonce.gitlabLastInfoDate;
+            delete assignment.gitlabLastInfoDate;
             // @ts-ignore
-            delete enonce.staff;
+            delete assignment.staff;
             // @ts-ignore
-            delete enonce.exercices;
+            delete assignment.exercises;
         }
 
-        return enonce ? req.session.sendResponse(res, StatusCodes.OK, enonce) : res.status(StatusCodes.NOT_FOUND).send();
+        return assignment ? req.session.sendResponse(res, StatusCodes.OK, assignment) : res.status(StatusCodes.NOT_FOUND).send();
     }
 
-    private async createEnonce(req: express.Request, res: express.Response) {
+    private async createAssignment(req: express.Request, res: express.Response) {
         const params: {
             name: string, members: Array<GitlabUser>, template: string
         } = req.body;
@@ -82,7 +75,7 @@ class EnonceRoutes implements RoutesManager {
 
         let repository: GitlabRepository;
         try {
-            repository = await GitlabManager.createRepository(params.name, Config.enonce.default.description.replace('{{ENONCE_NAME}}', params.name), Config.enonce.default.visibility, Config.enonce.default.initReadme, Config.gitlab.group.enonces, Config.enonce.default.sharedRunnersEnabled, Config.enonce.default.wikiEnabled, params.template);
+            repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template);
 
             await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
         } catch ( error ) {
@@ -107,31 +100,23 @@ class EnonceRoutes implements RoutesManager {
                 }
             }));
 
-            const enonce: Enonce = await db.enonce.create({
-                                                              data: {
-                                                                  name              : repository.name,
-                                                                  gitlabId          : repository.id,
-                                                                  gitlabLink        : repository.web_url,
-                                                                  gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
-                                                                  gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
-                                                                  gitlabLastInfoDate: new Date(),
-                                                                  staff             : {
-                                                                      connectOrCreate: [ ...params.members.map(gitlabUser => {
-                                                                          return {
-                                                                              create: {
-                                                                                  gitlabId : gitlabUser.id,
-                                                                                  firstname: gitlabUser.name
-                                                                              },
-                                                                              where : {
-                                                                                  gitlabId: gitlabUser.id
+            const assignment: Assignment = await db.assignment.create({
+                                                                          data: {
+                                                                              name: repository.name, gitlabId: repository.id, gitlabLink: repository.web_url, gitlabCreationInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfoDate: new Date(), staff: {
+                                                                                  connectOrCreate: [ ...params.members.map(gitlabUser => {
+                                                                                      return {
+                                                                                          create  : {
+                                                                                              gitlabId: gitlabUser.id, firstname: gitlabUser.name
+                                                                                          }, where: {
+                                                                                              gitlabId: gitlabUser.id
+                                                                                          }
+                                                                                      };
+                                                                                  }) ]
                                                                               }
-                                                                          };
-                                                                      }) ]
-                                                                  }
-                                                              }
-                                                          }) as unknown as Enonce;
+                                                                          }
+                                                                      }) as unknown as Assignment;
 
-            return req.session.sendResponse(res, StatusCodes.OK, enonce);
+            return req.session.sendResponse(res, StatusCodes.OK, assignment);
         } catch ( error ) {
             if ( error instanceof AxiosError ) {
                 return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
@@ -142,19 +127,18 @@ class EnonceRoutes implements RoutesManager {
         }
     }
 
-    private changeEnoncePublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
+    private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
         return async (req: express.Request, res: express.Response): Promise<void> => {
             try {
-                await GitlabManager.changeRepositoryVisibility(req.boundParams.enonce!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);
+                await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);
 
-                await db.enonce.update({
-                                           where: {
-                                               name: req.boundParams.enonce!.name
-                                           },
-                                           data : {
-                                               published: publish
-                                           }
-                                       });
+                await db.assignment.update({
+                                               where  : {
+                                                   name: req.boundParams.assignment!.name
+                                               }, data: {
+                        published: publish
+                    }
+                                           });
 
                 req.session.sendResponse(res, StatusCodes.OK);
             } catch ( error ) {
@@ -172,4 +156,4 @@ class EnonceRoutes implements RoutesManager {
 }
 
 
-export default new EnonceRoutes();
+export default new AssignmentRoutes();
diff --git a/ExpressAPI/src/routes/ExerciceRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts
similarity index 53%
rename from ExpressAPI/src/routes/ExerciceRoutes.ts
rename to ExpressAPI/src/routes/ExerciseRoutes.ts
index f4a2e38..e6f60a5 100644
--- a/ExpressAPI/src/routes/ExerciceRoutes.ts
+++ b/ExpressAPI/src/routes/ExerciseRoutes.ts
@@ -16,91 +16,76 @@ import { v4 as uuidv4 }               from 'uuid';
 import GitlabMember                   from '../shared/types/Gitlab/GitlabMember';
 import GitlabAccessLevel              from '../shared/types/Gitlab/GitlabAccessLevel';
 import { Prisma }                     from '@prisma/client';
-import { Enonce, Exercice }           from '../types/DatabaseTypes';
+import { Assignment, Exercise }       from '../types/DatabaseTypes';
 import db                             from '../helpers/DatabaseHelper';
 import SecurityCheckType              from '../types/SecurityCheckType';
 import GitlabTreeFile                 from '../shared/types/Gitlab/GitlabTreeFile';
 import GitlabFile                     from '../shared/types/Gitlab/GitlabFile';
-import EnonceFile                     from '../shared/types/Dojo/EnonceFile';
 import GitlabTreeFileType             from '../shared/types/Gitlab/GitlabTreeFileType';
 import JSON5                          from 'json5';
-import ExerciceResultsFile            from '../shared/types/Dojo/ExerciceResultsFile';
 import fs                             from 'fs';
 import path                           from 'path';
+import AssignmentFile                 from '../shared/types/Dojo/AssignmentFile';
+import ExerciseResultsFile            from '../shared/types/Dojo/ExerciseResultsFile';
 
 
-class ExerciceRoutes implements RoutesManager {
-    private readonly exerciceValidator: ExpressValidator.Schema = {
+class ExerciseRoutes implements RoutesManager {
+    private readonly exerciseValidator: ExpressValidator.Schema = {
         members: {
-            trim           : true,
-            notEmpty       : true,
-            customSanitizer: DojoValidators.jsonSanitizer
+            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
         }
     };
 
     private readonly resultValidator: ExpressValidator.Schema = {
-        exitCode     : {
-            isInt: true,
-            toInt: true
-        },
-        commit       : {
-            trim           : true,
-            notEmpty       : true,
-            customSanitizer: DojoValidators.jsonSanitizer
-        },
-        results      : {
-            trim           : true,
-            notEmpty       : true,
-            custom         : DojoValidators.exerciceResultsValidator,
-            customSanitizer: DojoValidators.jsonSanitizer
-        },
-        files        : {
-            trim           : true,
-            notEmpty       : true,
-            customSanitizer: DojoValidators.jsonSanitizer
-        },
-        archiveBase64: {
-            isBase64: true,
-            notEmpty: true
+        exitCode        : {
+            isInt: true, toInt: true
+        }, commit       : {
+            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
+        }, results      : {
+            trim: true, notEmpty: true, custom: DojoValidators.exerciseResultsValidator, customSanitizer: DojoValidators.jsonSanitizer
+        }, files        : {
+            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
+        }, archiveBase64: {
+            isBase64: true, notEmpty: true
         }
     };
 
     registerOnBackend(backend: Express) {
-        backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this));
+        backend.post('/assignments/:assignmentNameOrUrl/exercises', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciseValidator), this.createExercise.bind(this));
 
-        backend.get('/exercices/:exerciceId/enonce', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), this.getEnonce.bind(this));
+        backend.get('/exercises/:exerciseId/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this));
 
-        backend.post('/exercices/:exerciceId/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this));
+        backend.post('/exercises/:exerciseId/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this));
     }
 
-    private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
-        return `DojoEx - ${ enonce.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`;
+    private getExerciseName(assignment: Assignment, members: Array<GitlabUser>, suffix: number): string {
+        return `DojoEx - ${ assignment.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`;
     }
 
-    private getExercicePath(enonce: Enonce, exerciceId: string): string {
-        return `dojo-ex_${ (enonce.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciceId }`;
+    private getExercisePath(assignment: Assignment, exerciseId: string): string {
+        return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciseId }`;
     }
 
-    private async createExercice(req: express.Request, res: express.Response) {
+    private async createExercise(req: express.Request, res: express.Response) {
         const params: { members: Array<GitlabUser> } = req.body;
         params.members = [ await req.session.profile.gitlabProfile!.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id);
-        const enonce: Enonce = req.boundParams.enonce!;
+        const assignment: Assignment = req.boundParams.assignment!;
 
 
-        const exerciceId: string = uuidv4();
+        const exerciseId: string = uuidv4();
         const secret: string = uuidv4();
         let repository!: GitlabRepository;
 
         let suffix: number = 0;
         do {
             try {
-                repository = await GitlabManager.forkRepository((enonce.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciceName(enonce, params.members, suffix), this.getExercicePath(req.boundParams.enonce!, exerciceId), Config.exercice.default.description.replace('{{ENONCE_NAME}}', enonce.name), Config.exercice.default.visibility, Config.gitlab.group.exercices);
+                repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciseName(assignment, params.members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises);
 
                 await GitlabManager.protectBranch(repository.id, '*', false, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
 
-                await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_EXERCICE_ID', exerciceId, false, true);
+                await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_EXERCISE_ID', exerciseId, false, true);
                 await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, false, true);
-                await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercice.pipelineResultsFolder, false, false);
+                await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercise.pipelineResultsFolder, false, false);
 
                 break;
             } catch ( error ) {
@@ -114,14 +99,14 @@ class ExerciceRoutes implements RoutesManager {
                     return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
                 }
             }
-        } while ( suffix < Config.exercice.maxSameName );
+        } while ( suffix < Config.exercise.maxSameName );
 
-        if ( suffix >= Config.exercice.maxSameName ) {
+        if ( suffix >= Config.exercise.maxSameName ) {
             return res.status(StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE).send();
         }
 
         try {
-            await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/exercice_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
+            await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/exercise_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
         } catch ( error ) {
             logger.error(error);
 
@@ -133,7 +118,7 @@ class ExerciceRoutes implements RoutesManager {
         }
 
         try {
-            await Promise.all([ ...new Set([ ...enonce.staff.map(user => user.gitlabId), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => {
+            await Promise.all([ ...new Set([ ...assignment.staff.map(user => user.gitlabId), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => {
                 try {
                     return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
                 } catch ( e ) {
@@ -141,37 +126,26 @@ class ExerciceRoutes implements RoutesManager {
                 }
             }));
 
-            const exercice: Exercice = await db.exercice.create({
+            const exercise: Exercise = await db.exercise.create({
                                                                     data: {
-                                                                        id                : exerciceId,
-                                                                        enonceName        : enonce.name,
-                                                                        name              : repository.name,
-                                                                        secret            : secret,
-                                                                        gitlabId          : repository.id,
-                                                                        gitlabLink        : repository.web_url,
-                                                                        gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
-                                                                        gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
-                                                                        gitlabLastInfoDate: new Date(),
-                                                                        members           : {
+                                                                        id: exerciseId, assignmentName: assignment.name, name: repository.name, secret: secret, gitlabId: repository.id, gitlabLink: repository.web_url, gitlabCreationInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfoDate: new Date(), members: {
                                                                             connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                 return {
-                                                                                    create: {
-                                                                                        gitlabId : gitlabUser.id,
-                                                                                        firstname: gitlabUser.name
-                                                                                    },
-                                                                                    where : {
+                                                                                    create  : {
+                                                                                        gitlabId: gitlabUser.id, firstname: gitlabUser.name
+                                                                                    }, where: {
                                                                                         gitlabId: gitlabUser.id
                                                                                     }
                                                                                 };
                                                                             }) ]
                                                                         }
                                                                     }
-                                                                }) as unknown as Exercice;
+                                                                }) as unknown as Exercise;
 
-            return req.session.sendResponse(res, StatusCodes.OK, exercice);
+            return req.session.sendResponse(res, StatusCodes.OK, exercise);
         } catch ( error ) {
             logger.error(error);
-            
+
             if ( error instanceof AxiosError ) {
                 return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
             }
@@ -180,29 +154,29 @@ class ExerciceRoutes implements RoutesManager {
         }
     }
 
-    private async getEnonce(req: express.Request, res: express.Response) {
-        const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercice!.enonce.gitlabId);
+    private async getAssignment(req: express.Request, res: express.Response) {
+        const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
 
-        let enonceHjsonFile!: GitlabFile;
-        let immutableFiles: Array<GitlabFile> = await Promise.all(Config.enonce.baseFiles.map(async (baseFile: string) => {
-            let file = await GitlabManager.getFile(req.boundParams.exercice!.enonce.gitlabId, baseFile);
+        let assignmentHjsonFile!: GitlabFile;
+        let immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
+            let file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile);
 
-            if ( baseFile === Config.enonce.filename ) {
-                enonceHjsonFile = file;
+            if ( baseFile === Config.assignment.filename ) {
+                assignmentHjsonFile = file;
             }
 
             return file;
         }));
 
-        const dojoEnonceFile: EnonceFile = JSON5.parse(atob(enonceHjsonFile.content)) as EnonceFile;
+        const dojoAssignmentFile: AssignmentFile = JSON5.parse(atob(assignmentHjsonFile.content)) as AssignmentFile;
 
-        const immutablePaths = dojoEnonceFile.immutable.map(fileDescriptor => fileDescriptor.path);
+        const immutablePaths = dojoAssignmentFile.immutable.map(fileDescriptor => fileDescriptor.path);
 
         await Promise.all(repoTree.map(async gitlabTreeFile => {
             if ( gitlabTreeFile.type == GitlabTreeFileType.BLOB ) {
                 for ( const immutablePath of immutablePaths ) {
                     if ( gitlabTreeFile.path.startsWith(immutablePath) ) {
-                        immutableFiles.push(await GitlabManager.getFile(req.boundParams.exercice!.enonce.gitlabId, gitlabTreeFile.path));
+                        immutableFiles.push(await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, gitlabTreeFile.path));
                         break;
                     }
                 }
@@ -210,32 +184,25 @@ class ExerciceRoutes implements RoutesManager {
         }));
 
         return req.session.sendResponse(res, StatusCodes.OK, {
-            enonce    : (req.boundParams.exercice as Exercice).enonce,
-            enonceFile: dojoEnonceFile,
-            immutable : immutableFiles
+            assignment: (req.boundParams.exercise as Exercise).assignment, assignmentFile: dojoAssignmentFile, immutable: immutableFiles
         });
     }
 
     private async createResult(req: express.Request, res: express.Response) {
-        const params: { exitCode: number, commit: any, results: ExerciceResultsFile, files: any, archiveBase64: string } = req.body;
-        const exercice: Exercice = req.boundParams.exercice!;
+        const params: { exitCode: number, commit: any, results: ExerciseResultsFile, files: any, archiveBase64: string } = req.body;
+        const exercise: Exercise = req.boundParams.exercise!;
 
         const result = await db.result.create({
                                                   data: {
-                                                      exerciceId: exercice.id,
-                                                      exitCode  : params.exitCode,
-                                                      success   : params.results.success,
-                                                      commit    : params.commit,
-                                                      results   : params.results as unknown as Prisma.JsonObject,
-                                                      files     : params.files
+                                                      exerciseId: exercise.id, exitCode: params.exitCode, success: params.results.success, commit: params.commit, results: params.results as unknown as Prisma.JsonObject, files: params.files
                                                   }
                                               });
 
-        fs.writeFileSync(path.join(Config.getResultsFolder(exercice), `${ result.dateTime.toISOString().replace(/:/g, '_') }.tar.gz`), params.archiveBase64, 'base64');
+        fs.writeFileSync(path.join(Config.getResultsFolder(exercise), `${ result.dateTime.toISOString().replace(/:/g, '_') }.tar.gz`), params.archiveBase64, 'base64');
 
         req.session.sendResponse(res, StatusCodes.OK);
     }
 }
 
 
-export default new ExerciceRoutes();
+export default new ExerciseRoutes();
diff --git a/ExpressAPI/src/routes/GitlabRoutes.ts b/ExpressAPI/src/routes/GitlabRoutes.ts
index ad03cfc..40ad2d9 100644
--- a/ExpressAPI/src/routes/GitlabRoutes.ts
+++ b/ExpressAPI/src/routes/GitlabRoutes.ts
@@ -6,7 +6,7 @@ import SecurityCheckType  from '../types/SecurityCheckType';
 import GitlabManager      from '../managers/GitlabManager';
 
 
-class EnonceRoutes implements RoutesManager {
+class GitlabRoutes implements RoutesManager {
     registerOnBackend(backend: Express) {
         backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess);
     }
@@ -19,4 +19,4 @@ class EnonceRoutes implements RoutesManager {
 }
 
 
-export default new EnonceRoutes();
+export default new GitlabRoutes();
diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared
index f33e4e0..4cbcc73 160000
--- a/ExpressAPI/src/shared
+++ b/ExpressAPI/src/shared
@@ -1 +1 @@
-Subproject commit f33e4e0c7b34f9060e8995550920d25cd3e73c40
+Subproject commit 4cbcc7398459d7a2de027292fa0522c7fa592935
diff --git a/ExpressAPI/src/types/DatabaseTypes.ts b/ExpressAPI/src/types/DatabaseTypes.ts
index 4627dcb..bdb25f8 100644
--- a/ExpressAPI/src/types/DatabaseTypes.ts
+++ b/ExpressAPI/src/types/DatabaseTypes.ts
@@ -3,27 +3,27 @@ import LazyVal    from '../shared/helpers/LazyVal';
 import GitlabUser from '../shared/types/Gitlab/GitlabUser';
 
 
-const userBase = Prisma.validator<Prisma.UserArgs>()({
-                                                         include: { exercices: true }
-                                                     });
-const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({
-                                                             include: {
-                                                                 exercices: true,
-                                                                 staff    : true
-                                                             }
-                                                         });
-const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({
-                                                                 include: {
-                                                                     enonce : true,
-                                                                     members: true,
-                                                                     results: true
-                                                                 }
-                                                             });
-const resultBase = Prisma.validator<Prisma.ResultArgs>()({
-                                                             include: {
-                                                                 exercice: true
-                                                             }
-                                                         });
+const userBase = Prisma.validator<Prisma.UserDefaultArgs>()({
+                                                                include: { exercises: true }
+                                                            });
+const assignmentBase = Prisma.validator<Prisma.AssignmentDefaultArgs>()({
+                                                                            include: {
+                                                                                exercises: true,
+                                                                                staff    : true
+                                                                            }
+                                                                        });
+const exerciseBase = Prisma.validator<Prisma.ExerciseDefaultArgs>()({
+                                                                        include: {
+                                                                            assignment: true,
+                                                                            members   : true,
+                                                                            results   : true
+                                                                        }
+                                                                    });
+const resultBase = Prisma.validator<Prisma.ResultDefaultArgs>()({
+                                                                    include: {
+                                                                        exercise: true
+                                                                    }
+                                                                });
 
 
 export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & {
@@ -31,6 +31,6 @@ export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & {
     isTeachingStaff: boolean
     gitlabProfile: LazyVal<GitlabUser>
 }
-export type Enonce = Prisma.EnonceGetPayload<typeof enonceBase>
-export type Exercice = Prisma.ExerciceGetPayload<typeof exerciceBase>
+export type Assignment = Prisma.AssignmentGetPayload<typeof assignmentBase>
+export type Exercise = Prisma.ExerciseGetPayload<typeof exerciseBase>
 export type Result = Prisma.ResultGetPayload<typeof resultBase>
\ No newline at end of file
diff --git a/ExpressAPI/src/types/SecurityCheckType.ts b/ExpressAPI/src/types/SecurityCheckType.ts
index b063ff9..3a0b733 100644
--- a/ExpressAPI/src/types/SecurityCheckType.ts
+++ b/ExpressAPI/src/types/SecurityCheckType.ts
@@ -1,8 +1,8 @@
 enum SecurityCheckType {
-    TEACHING_STAFF      = 'teachingStaff',
-    ENONCE_STAFF        = 'enonceStaff',
-    ENONCE_IS_PUBLISHED = 'enonceIsPublished',
-    EXERCICE_SECRET     = 'exerciceSecret',
+    TEACHING_STAFF          = 'teachingStaff',
+    ASSIGNMENT_STAFF        = 'assignmentStaff',
+    ASSIGNMENT_IS_PUBLISHED = 'assignmentIsPublished',
+    EXERCISE_SECRET         = 'exerciseSecret',
 }
 
 
diff --git a/ExpressAPI/src/types/express/index.d.ts b/ExpressAPI/src/types/express/index.d.ts
index c33801d..e7ac814 100644
--- a/ExpressAPI/src/types/express/index.d.ts
+++ b/ExpressAPI/src/types/express/index.d.ts
@@ -1,5 +1,5 @@
-import Session              from '../../controllers/Session';
-import { Enonce, Exercice } from '../DatabaseTypes';
+import Session                  from '../../controllers/Session';
+import { Assignment, Exercise } from '../DatabaseTypes';
 
 // to make the file a module and avoid the TypeScript error
 export {};
@@ -9,7 +9,7 @@ declare global {
         export interface Request {
             session: Session,
             boundParams: {
-                enonce: Enonce | undefined, exercice: Exercice | undefined
+                assignment: Assignment | undefined, exercise: Exercise | undefined
             }
         }
     }
-- 
GitLab