diff --git a/ExpressAPI/prisma/migrations/20240228143135_add_deleted_to_assignment/migration.sql b/ExpressAPI/prisma/migrations/20240228143135_add_deleted_to_assignment/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0e61d6e85aa2b30a48335d301cdb0ce94e91b926
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20240228143135_add_deleted_to_assignment/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE `Assignment` ADD COLUMN `deleted` BOOLEAN NOT NULL DEFAULT false;
diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma
index a68226e0868e475d54557f330fa35699e2f34bf7..52ee5ab3cfe6fc10d85f047367dadb1058eab018 100644
--- a/ExpressAPI/prisma/schema.prisma
+++ b/ExpressAPI/prisma/schema.prisma
@@ -34,6 +34,7 @@ model Assignment {
     gitlabLastInfo     Json     @db.Json
     gitlabLastInfoDate DateTime
     published          Boolean  @default(false)
+    deleted            Boolean  @default(false)
 
     exercises Exercise[]
     staff     User[]
diff --git a/ExpressAPI/src/managers/ExerciseManager.ts b/ExpressAPI/src/managers/ExerciseManager.ts
index 66cfda8a89bb8f8d726f03c54a2bdcf915b45914..640d334f4056901939d5bc41d1d42774ac1c6077 100644
--- a/ExpressAPI/src/managers/ExerciseManager.ts
+++ b/ExpressAPI/src/managers/ExerciseManager.ts
@@ -6,20 +6,20 @@ import db           from '../helpers/DatabaseHelper';
 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 Exercise ?? undefined;
+            where  : {
+                id: id
+            },
+            include: include
+        }) as unknown as Exercise ?? undefined;
     }
-
+    
     async getFromAssignment(assignmentName: string, include: Prisma.ExerciseInclude | undefined = undefined): Promise<Array<Exercise> | undefined> {
         return await db.exercise.findMany({
-                                              where  : {
-                                                  assignmentName: assignmentName
-                                              },
-                                              include: include
-                                          }) as Array<Exercise> ?? undefined;
+            where  : {
+                assignmentName: assignmentName
+            },
+            include: include
+        }) as Array<Exercise> ?? undefined;
     }
 }
 
diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts
index 390fba57640e11b4514caa37df8131a4b9622886..2bef23ac06f69d353e0e5fac255634fc0607343b 100644
--- a/ExpressAPI/src/managers/GitlabManager.ts
+++ b/ExpressAPI/src/managers/GitlabManager.ts
@@ -260,6 +260,16 @@ class GitlabManager {
         });
 
     }
+
+    async removeRepositoryMember(repoId : number, userId : number, skipSubresources: boolean = false, unassignIssuables : boolean = false) {
+        const response = await axios.delete<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBER_DELETE).replace('{{id}}', String(repoId)).replace('{{user_id}}', String(userId)), {
+            data: {
+                user_id     : userId,
+            }
+        });
+
+        return response.data;
+    }
 }
 
 
diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
index b01c230d6c262f955256fb5412040010f1ddc818..ebb2f29927062f4c8ce43a7863f5211218d8756c 100644
--- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
+++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
@@ -3,6 +3,7 @@ import express           from 'express';
 import { StatusCodes }   from 'http-status-codes';
 import ExerciseManager   from '../managers/ExerciseManager';
 import AssignmentManager from '../managers/AssignmentManager';
+// import UserManager       from '../managers/UserManager';
 
 
 type GetFunction = (id: string | number, ...args: Array<unknown>) => Promise<unknown>
@@ -45,6 +46,10 @@ class ParamsCallbackManager {
             members   : true,
             results   : true
         } ], 'exercise');
+
+        // this.listenParam('userId', backend, (UserManager.getById as GetFunction).bind(UserManager), [ {
+        //     user: true,
+        // } ], 'user');
     }
 }
 
diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
index 6129686931d9bfe84a6783bd748533c2de3af9aa..b9881afdc585592e76856b81a58ab81efe602d8d 100644
--- a/ExpressAPI/src/routes/AssignmentRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -1,7 +1,7 @@
 import { Express }                    from 'express-serve-static-core';
 import express                        from 'express';
 import * as ExpressValidator          from 'express-validator';
-import { StatusCodes }                from 'http-status-codes';
+import { NON_AUTHORITATIVE_INFORMATION, StatusCodes }                from 'http-status-codes';
 import RoutesManager                  from '../express/RoutesManager';
 import ParamsValidatorMiddleware      from '../middlewares/ParamsValidatorMiddleware';
 import SecurityMiddleware             from '../middlewares/SecurityMiddleware';
@@ -50,9 +50,8 @@ class AssignmentRoutes implements RoutesManager {
         
         backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.publishAssignment.bind(this));
         backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unpublishAssignment.bind(this));
-        
-        // backend.get('/assignments/:onlyMine', SecurityMiddleware.check(false, SecurityCheckType.TEACHING_STAFF),this.getAssignmentById.bind(this));
-        backend.get('/assignmentss', SecurityMiddleware.check(false, SecurityCheckType.TEACHING_STAFF),this.getAssignmentById.bind(this));
+
+        backend.patch('/assignments/:assignmentNameOrUrl/deleted', SecurityMiddleware.check(false, SecurityCheckType.ASSIGNMENT_STAFF), this.deleteAssignment.bind(this));
     }
     // Get an assignment by its name or gitlab url
     private async getAssignment(req: express.Request, res: express.Response) {
@@ -92,7 +91,6 @@ class AssignmentRoutes implements RoutesManager {
         params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ];
         params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id);
         
-        
         let repository: GitlabRepository;
         try {
             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);
@@ -214,25 +212,22 @@ class AssignmentRoutes implements RoutesManager {
         };
     }
     
-    private async getAssignmentById(req : express.Request, res : express.Response) {
-        // const onlyMine = req.boundParams.user?.id;
-        // const users = await db.assignment.findMany();
-        // console.log(onlyMine);
-        // const assign = await db.user.findUnique({
-        //     where: {
-        //       id: onlyMine,
-        //     },
-        //     include: {
-        //       assignments: true,
-        //     },
-        //   });
-        const assign = await db.assignment.findMany();
-        return req.session.sendResponse(res, StatusCodes.OK, assign);
+    private async deleteAssignment(req : express.Request, res : express.Response) {
+        const nameAssignment = req.params.assignmentNameOrUrl;
+
+        await GitlabManager.removeRepositoryMember(13893, 627);
+
+        await db.assignment.update({
+            where : {
+                name : nameAssignment
+            },
+            data : {
+                deleted : true
+            }
+        });
+
+        return req.session.sendResponse(res, StatusCodes.OK);
     }
-    
-    // private async deleteAssignment(req : express.Request, res : express.Response) {
-        
-    // }
 }
 
 
diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts
index a0b7a3498d55c807340fe3d70cb9cd53794c6d06..ff572a7da91efb21f9889439c416f10f652d37e7 100644
--- a/ExpressAPI/src/routes/ExerciseRoutes.ts
+++ b/ExpressAPI/src/routes/ExerciseRoutes.ts
@@ -105,7 +105,7 @@ class ExerciseRoutes implements RoutesManager {
             return req.session.sendResponse(res, StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE, reachedLimitUsers, 'Max exercise per assignment reached', DojoStatusCode.MAX_EXERCISE_PER_ASSIGNMENT_REACHED);
         }
         
-        
+
         const exerciseId: string = uuidv4();
         const secret: string = uuidv4();
         let repository!: GitlabRepository;
diff --git a/ExpressAPI/src/routes/UserRoutes.ts b/ExpressAPI/src/routes/UserRoutes.ts
index d514b7b1dc4bff53684cb62c3f782e2ed4189e39..1841a5d4ad5f8714112a579bb71a595b56504cbd 100644
--- a/ExpressAPI/src/routes/UserRoutes.ts
+++ b/ExpressAPI/src/routes/UserRoutes.ts
@@ -9,14 +9,50 @@ import { StatusCodes } from 'http-status-codes';
 
 class UserRoutes implements RoutesManager {
      registerOnBackend(backend: Express): void {
-          backend.get('/users', SecurityMiddleware.check(false, SecurityCheckType.ADMIN), this.getUsers.bind(this));
-          // backend.patch('/users/:userId/role', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.changeRole.bind(this))
+          backend.get('/users', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.getUsers.bind(this));
+          backend.patch('/users/:userId/role', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.changeRole.bind(this))
+          backend.get('/users/:userId/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.getUsersAssignments.bind(this));
      }
      
      private async getUsers(req: express.Request, res: express.Response) {          
-          const users = await db.user.findMany()
+          const users = await db.user.findMany();
           return req.session.sendResponse(res, StatusCodes.OK, users);
      }
+     
+     private async getUsersAssignments(req: express.Request, res: express.Response) {
+          const id = +req.params.userId;
+          const user = await db.user.findUnique({
+               where: { 
+                    id: id,
+               },
+               include: {
+                    assignments: true,
+
+               }, // Include the assignments related to the user
+          });
+          return req.session.sendResponse(res, StatusCodes.OK, user);    
+     }
+     
+     private async changeRole(req: express.Request, res: express.Response) {
+          const id = +req.params.userId;
+          const newRole = req.body.newRole;
+          // check admin
+          
+          if (id != req.session.profile.id) {
+               
+               await db.user.update({
+                    where : {
+                         id : id
+                    },
+                    data : {
+                         role: newRole
+                    }
+               });
+               req.session.sendResponse(res, StatusCodes.OK);
+          } else {
+               req.session.sendResponse(res, StatusCodes.FORBIDDEN);
+          }
+     }
 }
 
 export default new UserRoutes();
diff --git a/ExpressAPI/src/shared/.gitignore b/ExpressAPI/src/shared/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..45de4116fd5ee5a5a2a2612157301ce75d7bffed
--- /dev/null
+++ b/ExpressAPI/src/shared/.gitignore
@@ -0,0 +1,348 @@
+.env
+
+aws.xml
+workspace.xml
+
+############################ MacOS
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+############################ Windows
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+
+############################ Linux
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+nohup.out
+
+
+############################ Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+
+############################ Dropbox
+# Dropbox settings and caches
+.dropbox
+.dropbox.attr
+.dropbox.cache
+
+
+########################### Microsoft Office
+*.tmp
+
+# Word temporary
+~$*.doc*
+
+# Excel temporary
+~$*.xls*
+
+# Excel Backup File
+*.xlk
+
+# PowerPoint temporary
+~$*.ppt*
+
+# Visio autosave temporary files
+*.~vsd*
+
+# LibreOffice locks
+.~lock.*#
+
+########################### Node
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+
+########################### Gitlab Runner
+builds/
+cache/
+
+
+########################### JetBrains
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+
+########################### Vim
+# Swap
+[._]*.s[a-v][a-z]
+!*.svg  # comment out if you don't need vector files
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+*~
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
diff --git a/ExpressAPI/src/shared/LICENSE b/ExpressAPI/src/shared/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..ca4fe13006a1fcb8fa078da3d6ebbe34213cb20a
--- /dev/null
+++ b/ExpressAPI/src/shared/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    NodeSharedCode
+    Copyright (C) 2023  ISC / projects / Dojo / Projects / Shared
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/ExpressAPI/src/shared/README.md b/ExpressAPI/src/shared/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e0c65cf50afb02dc946e602fd08b2e5102f96e04
--- /dev/null
+++ b/ExpressAPI/src/shared/README.md
@@ -0,0 +1,21 @@
+# NodeSharedCode
+
+This repo contains some code that can be shared across node projects of Dojo.
+
+## Prerequisites
+
+These packages are needed :
+
+- `json5`
+- `tar-stream`
+- `winston`
+- `zod`
+- `zod-validation-error`
+
+## How to use it
+
+By adding this repo as submodule
+
+```bash
+git submodule add ../../shared/nodesharedcode.git shared
+```
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/config/SharedConfig.ts b/ExpressAPI/src/shared/config/SharedConfig.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0f8f8b0397b4a855f9a761934e7a37f5a9e6b9a3
--- /dev/null
+++ b/ExpressAPI/src/shared/config/SharedConfig.ts
@@ -0,0 +1,47 @@
+class SharedConfig {
+    public readonly production: boolean;
+    public debug: boolean = false;
+
+    public readonly logsFolder: string;
+
+    public gitlab: {
+        URL: string, apiURL: string
+    };
+
+    public readonly login: {
+        gitlab: {
+            client: {
+                id: string
+            }, url: {
+                redirect: string, token: string
+            }
+        }
+    };
+
+
+    constructor() {
+        this.production = process.env.NODE_ENV === 'production';
+
+        this.logsFolder = process.env.LOGS_FOLDER || '';
+
+        this.gitlab = {
+            URL   : process.env.GITLAB_URL || '',
+            apiURL: process.env.GITLAB_API_URL || ''
+        };
+
+        this.login = {
+            gitlab: {
+                client: {
+                    id: process.env.LOGIN_GITLAB_CLIENT_ID || ''
+                },
+                url   : {
+                    redirect: process.env.LOGIN_GITLAB_URL_REDIRECT || '',
+                    token   : process.env.LOGIN_GITLAB_URL_TOKEN || ''
+                }
+            }
+        };
+    }
+}
+
+
+export default new SharedConfig();
diff --git a/ExpressAPI/src/shared/helpers/ArchiveHelper.ts b/ExpressAPI/src/shared/helpers/ArchiveHelper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4449d6e8f4953227e0b422bb0a647a85432351c
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/ArchiveHelper.ts
@@ -0,0 +1,69 @@
+import fs           from 'node:fs';
+import path         from 'node:path';
+import tar          from 'tar-stream';
+import stream       from 'node:stream';
+import { Writable } from 'stream';
+import zlib         from 'zlib';
+
+
+class ArchiveHelper {
+    private async explore(absoluteBasePath: string, rootPath: string, pack: tar.Pack) {
+        for ( let file of await fs.promises.readdir(rootPath) ) {
+            if ( file === 'output.tar' ) {
+                continue;
+            }
+            file = path.join(rootPath, file);
+            const stat = await fs.promises.stat(file);
+            if ( stat.isDirectory() ) {
+                await this.explore(absoluteBasePath, file, pack);
+                continue;
+            }
+            const entry = pack.entry({
+                                         name: file.replace(absoluteBasePath, ''),
+                                         size: stat.size
+                                     }, (err) => {
+                if ( err ) {
+                    throw err;
+                }
+            });
+            const stream = fs.createReadStream(file);
+            stream.pipe(entry);
+        }
+    }
+
+    private async compress(folderPath: string, tarDataStream: stream.Writable) {
+        const pack = tar.pack();
+
+        await this.explore(folderPath, folderPath, pack);
+
+        pack.pipe(zlib.createGzip()).pipe(tarDataStream);
+        pack.finalize();
+    }
+
+    public async getBase64(folderPath: string): Promise<string> {
+        let data: string;
+        const tarDataStream = new stream.Writable({
+                                                      write(this: Writable, chunk: Buffer, _encoding: BufferEncoding, next: (error?: Error | null) => void) {
+                                                          if ( data ) {
+                                                              data += chunk.toString('hex');
+                                                          } else {
+                                                              data = chunk.toString('hex');
+                                                          }
+                                                          next();
+                                                      }
+                                                  });
+
+        await this.compress(folderPath, tarDataStream);
+
+        data = await (new Promise((resolve) => {
+            tarDataStream.on('close', () => {
+                resolve(data);
+            });
+        }));
+
+        return Buffer.from(data, 'hex').toString('base64');
+    }
+}
+
+
+export default new ArchiveHelper();
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/helpers/Dojo/SharedAssignmentHelper.ts b/ExpressAPI/src/shared/helpers/Dojo/SharedAssignmentHelper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b103f8cc526ed60a9aa6b7c5276557bcf1ab6635
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/Dojo/SharedAssignmentHelper.ts
@@ -0,0 +1,56 @@
+import AssignmentFile       from '../../types/Dojo/AssignmentFile';
+import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus';
+import DojoStatusCode       from '../../types/Dojo/DojoStatusCode';
+import GitlabPipeline       from '../../types/Gitlab/GitlabPipeline';
+import SharedGitlabManager  from '../../managers/SharedGitlabManager';
+import Json5FileValidator   from '../Json5FileValidator';
+
+
+class SharedAssignmentHelper {
+    validateDescriptionFile(filePathOrStr: string, isFile: boolean = true, version: number = 1): { content: AssignmentFile | undefined, isValid: boolean, error: string | null } {
+        switch ( version ) {
+            case 1:
+                return Json5FileValidator.validateFile(AssignmentFile, filePathOrStr, isFile);
+            default:
+                return {
+                    content: undefined,
+                    isValid: false,
+                    error  : `Version ${ version } not supported`
+                };
+        }
+    }
+
+    async isPublishable(repositoryId: number): Promise<{ isPublishable: boolean, lastPipeline: GitlabPipeline | null, status?: { code: DojoStatusCode, message: string } }> {
+        const pipelines = await SharedGitlabManager.getRepositoryPipelines(repositoryId, 'main');
+        if ( pipelines.length > 0 ) {
+            const lastPipeline = pipelines[0];
+            if ( lastPipeline.status != GitlabPipelineStatus.SUCCESS ) {
+                return {
+                    isPublishable: false,
+                    lastPipeline : lastPipeline,
+                    status       : {
+                        code   : DojoStatusCode.ASSIGNMENT_PUBLISH_PIPELINE_FAILED,
+                        message: `Last pipeline status is not "${ GitlabPipelineStatus.SUCCESS }" but "${ lastPipeline.status }".`
+                    }
+                };
+            } else {
+                return {
+                    isPublishable: true,
+                    lastPipeline : lastPipeline
+                };
+            }
+        } else {
+            return {
+                isPublishable: false,
+                lastPipeline : null,
+                status       : {
+                    code   : DojoStatusCode.ASSIGNMENT_PUBLISH_NO_PIPELINE,
+                    message: 'No pipeline found for this assignment.'
+                }
+            };
+        }
+    }
+}
+
+
+export default new SharedAssignmentHelper();
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/helpers/Dojo/SharedExerciseHelper.ts b/ExpressAPI/src/shared/helpers/Dojo/SharedExerciseHelper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..97ccf7c9a39682ba8b14fa65b54a89d9898ebf3c
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/Dojo/SharedExerciseHelper.ts
@@ -0,0 +1,4 @@
+class SharedExerciseHelper {}
+
+
+export default new SharedExerciseHelper();
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/helpers/Json5FileValidator.ts b/ExpressAPI/src/shared/helpers/Json5FileValidator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9be717bbf249bdb6230b187deb1e1fc948a2274
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/Json5FileValidator.ts
@@ -0,0 +1,46 @@
+import JSON5            from 'json5';
+import fs               from 'fs';
+import { z, ZodError }  from 'zod';
+import { fromZodError } from 'zod-validation-error';
+
+
+class Json5FileValidator {
+    validateFile<T>(schema: z.ZodType<T>, filePathOrStr: string, isFile: boolean = true, resultSanitizer: (value: T) => T = value => value): { content: T | undefined, isValid: boolean, error: string | null } {
+        let parsedInput: T;
+
+        try {
+            parsedInput = JSON5.parse(isFile ? fs.readFileSync(filePathOrStr, 'utf8') : filePathOrStr);
+        } catch ( error ) {
+            return {
+                content: undefined,
+                isValid: false,
+                error  : `JSON5 invalid : ${ JSON.stringify(error) }`
+            };
+        }
+
+        try {
+            return {
+                content: resultSanitizer(schema.parse(parsedInput) as unknown as T),
+                isValid: true,
+                error  : null
+            };
+        } catch ( error ) {
+            if ( error instanceof ZodError ) {
+                return {
+                    content: parsedInput,
+                    isValid: false,
+                    error  : fromZodError(error).toString()
+                };
+            }
+
+            return {
+                content: parsedInput,
+                isValid: false,
+                error  : `Unknown error : ${ JSON.stringify(error) }`
+            };
+        }
+    }
+}
+
+
+export default new Json5FileValidator();
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/helpers/LazyVal.ts b/ExpressAPI/src/shared/helpers/LazyVal.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4613c4b535706f5853b060279ce46a957e3c3030
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/LazyVal.ts
@@ -0,0 +1,29 @@
+class LazyVal<T> {
+    private val: T | undefined = undefined;
+
+    constructor(private valLoader: () => Promise<T> | T) {}
+
+    get value(): Promise<T> {
+        return new Promise<T>((resolve) => {
+            if ( this.val === undefined ) {
+                Promise.resolve(this.valLoader()).then((value: T) => {
+                    this.val = value;
+                    resolve(value);
+                });
+            } else {
+                resolve(this.val);
+            }
+        });
+    }
+
+    reset() {
+        this.val = undefined;
+    }
+
+    get isValueLoaded(): boolean {
+        return this.val !== undefined;
+    }
+}
+
+
+export default LazyVal;
diff --git a/ExpressAPI/src/shared/helpers/Toolbox.ts b/ExpressAPI/src/shared/helpers/Toolbox.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8a76bebb202e6bcbe46944e562924effdeb26afe
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/Toolbox.ts
@@ -0,0 +1,56 @@
+import fs   from 'fs/promises';
+import path from 'path';
+
+
+class Toolbox {
+    public urlToPath(url: string): string {
+        return url.replace(/^([a-z]{3,5}:\/{2})?[a-z.@]+(:[0-9]{1,5})?.(.*)/, '$3').replace('.git', '');
+    }
+
+    /*
+     Source of getAllFiles and getTotalSize (modified for this project): https://coderrocketfuel.com/article/get-the-total-size-of-all-files-in-a-directory-using-node-js
+     */
+    private async getAllFiles(dirPath: string, arrayOfFiles: Array<string> = []): Promise<Array<string>> {
+        const files = await fs.readdir(dirPath);
+
+        await Promise.all(files.map(async file => {
+            if ( (await fs.stat(dirPath + '/' + file)).isDirectory() ) {
+                arrayOfFiles = await this.getAllFiles(dirPath + '/' + file, arrayOfFiles);
+            } else {
+                arrayOfFiles.push(path.join(dirPath, file));
+            }
+        }));
+
+        return arrayOfFiles;
+    }
+
+    private async getTotalSize(directoryPath: string): Promise<number> {
+        const arrayOfFiles = await this.getAllFiles(directoryPath);
+
+        let totalSize = 0;
+
+        for ( const filePath of arrayOfFiles ) {
+            totalSize += (await fs.stat(filePath)).size;
+        }
+
+        return totalSize;
+    }
+
+    get fs() {
+        return {
+            getAllFiles : this.getAllFiles.bind(this),
+            getTotalSize: this.getTotalSize.bind(this)
+        };
+    }
+
+    public snakeToCamel(str: string): string {
+        return str.toLowerCase().replace(/([-_][a-z])/g, (group: string) => group.toUpperCase().replace('-', '').replace('_', ''));
+    }
+
+    public getKeysWithPrefix(obj: object, prefix: string): Array<string> {
+        return Object.keys(obj).filter(key => key.startsWith(prefix));
+    }
+}
+
+
+export default new Toolbox();
diff --git a/ExpressAPI/src/shared/helpers/TypeScriptExtensions.ts b/ExpressAPI/src/shared/helpers/TypeScriptExtensions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd730ad37daa50a1b6e855efaf3c48ce739faa05
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/TypeScriptExtensions.ts
@@ -0,0 +1,76 @@
+declare global {
+    interface BigInt {
+        toJSON: () => string;
+    }
+
+
+    interface Array<T> {
+        removeObjectDuplicates: (getProperty: (item: T) => unknown) => Array<T>;
+    }
+
+
+    interface String {
+        toBoolean: () => boolean;
+        capitalizingFirstLetter: () => string;
+        capitalizeName: () => string;
+        convertWithEnvVars: () => string;
+    }
+}
+
+function registerAll() {
+    registerBigIntJson();
+    registerArrayRemoveObjectDuplicates();
+    registerStringToBoolean();
+    registerStringCapitalizingFirstLetter();
+    registerStringCapitalizeName();
+    registerStringConvertWithEnvVars();
+}
+
+function registerBigIntJson() {
+    BigInt.prototype.toJSON = function () {
+        return this.toString();
+    };
+}
+
+function registerArrayRemoveObjectDuplicates() {
+    Array.prototype.removeObjectDuplicates = function <T>(this: Array<T>, getProperty: (item: T) => unknown): Array<T> {
+        return this.reduce((accumulator: Array<T>, current: T) => {
+            if ( !accumulator.find((item: T) => getProperty(item) === getProperty(current)) ) {
+                accumulator.push(current);
+            }
+            return accumulator;
+        }, Array<T>());
+    };
+}
+
+function registerStringToBoolean() {
+    String.prototype.toBoolean = function (this: string): boolean {
+        const tmp = this.toLowerCase().trim();
+        return tmp === 'true' || tmp === '1';
+    };
+}
+
+function registerStringCapitalizingFirstLetter() {
+    String.prototype.capitalizingFirstLetter = function (this: string): string {
+        return this.charAt(0).toUpperCase() + this.slice(1);
+    };
+}
+
+function registerStringCapitalizeName() {
+    String.prototype.capitalizeName = function (this: string): string {
+        return this.trim().replace(/(?:^|\s|-)\S/g, s => s.toUpperCase());
+    };
+}
+
+function registerStringConvertWithEnvVars() {
+    String.prototype.convertWithEnvVars = function (this: string): string {
+        return this.replace(/\${?([a-zA-Z0-9_]+)}?/g, (_match: string, p1: string) => {
+            return process.env[p1] || '';
+        });
+    };
+}
+
+registerAll();
+
+
+export default null;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/helpers/recursiveFilesStats/README.md b/ExpressAPI/src/shared/helpers/recursiveFilesStats/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f2e98d4319eb3ce841bd1b2bb57c1b393aa8f483
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/recursiveFilesStats/README.md
@@ -0,0 +1,148 @@
+Source: recursive-readdir-files
+===
+Modified for Dojo
+===
+
+## Usage
+
+```js
+import recursiveReaddirFiles from 'recursive-readdir-files';
+
+
+const files = await recursiveReaddirFiles(process.cwd(), {
+    ignored: /\/(node_modules|\.git)/
+});
+
+// `files` is an array
+console.log(files);
+// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+// [
+//   {
+//     dev: 16777233,
+//     mode: 33188,
+//     nlink: 1,
+//     uid: 501,
+//     gid: 20,
+//     rdev: 0,
+//     blksize: 4096,
+//     ino: 145023089,
+//     size: 89,
+//     blocks: 8,
+//     atimeMs: 1649303678077.934,
+//     mtimeMs: 1649303676847.1777,
+//     ctimeMs: 1649303676847.1777,
+//     birthtimeMs: 1649301118132.6782,
+//     atime: 2022-04-07T03:54:38.078Z,
+//     mtime: 2022-04-07T03:54:36.847Z,
+//     ctime: 2022-04-07T03:54:36.847Z,
+//     birthtime: 2022-04-07T03:11:58.133Z,
+//     name: 'watch.ts',
+//     path: '/Users/xxx/watch.ts',
+//     ext: 'ts'
+//   },
+//   // ...
+// ]
+```
+
+Or
+
+```js
+recursiveReaddirFiles(process.cwd(), {
+    ignored: /\/(node_modules|\.git)/
+}, (filepath, state) => {
+    console.log(filepath);
+    // 👉 /Users/xxx/watch.ts
+    console.log(state.isFile());      // 👉 true
+    console.log(state.isDirectory()); // 👉 false
+    console.log(state);
+    // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+    // {
+    //   dev: 16777233,
+    //   mode: 33188,
+    //   nlink: 1,
+    //   uid: 501,
+    //   gid: 20,
+    //   rdev: 0,
+    //   blksize: 4096,
+    //   ino: 145023089,
+    //   size: 89,
+    //   blocks: 8,
+    //   atimeMs: 1649303678077.934,
+    //   mtimeMs: 1649303676847.1777,
+    //   ctimeMs: 1649303676847.1777,
+    //   birthtimeMs: 1649301118132.6782,
+    //   atime: 2022-04-07T03:54:38.078Z,
+    //   mtime: 2022-04-07T03:54:36.847Z,
+    //   ctime: 2022-04-07T03:54:36.847Z,
+    //   birthtime: 2022-04-07T03:11:58.133Z,
+    //   name: 'watch.ts',
+    //   path: '/Users/xxx/watch.ts',
+    //   ext: 'ts'
+    // }
+})
+```
+
+## Options
+
+```ts
+export interface RecursiveReaddirFilesOptions {
+    /**
+     * Ignore files
+     * @example `/\/(node_modules|\.git)/`
+     */
+    ignored?: RegExp;
+    /**
+     * Specifies a list of `glob` patterns that match files to be included in compilation.
+     * @example `/(\.json)$/`
+     */
+    include?: RegExp;
+    /**
+     * Specifies a list of files to be excluded from compilation.
+     * @example `/(package\.json)$/`
+     */
+    exclude?: RegExp;
+    /** Provide filtering methods to filter data. */
+    filter?: (item: IFileDirStat) => boolean;
+    /** Do not give the absolute path but the relative one from the root folder */
+    replacePathByRelativeOne?: boolean;
+    /** Remove stats that are not necessary for transfert */
+    liteStats?: boolean;
+}
+```
+
+## Result
+
+```ts
+import fs from 'node:fs';
+
+
+export interface IFileDirStat extends Partial<fs.Stats> {
+    /**
+     * @example `/a/sum.jpg` => `sum.jpg`
+     */
+    name: string;
+    /**
+     * @example `/basic/src/utils/sum.ts`
+     */
+    path: string;
+    /**
+     * @example `/a/b.jpg` => `jpg`
+     */
+    ext?: string;
+}
+
+
+declare type Callback = (filepath: string, stat: IFileDirStat) => void;
+export default function recursiveReaddirFiles(rootPath: string, options?: RecursiveReaddirFilesOptions, callback?: Callback): Promise<IFileDirStat[]>;
+export { recursiveReaddirFiles };
+export declare const getStat: (filepath: string) => Promise<IFileDirStat>;
+/**
+ * Get ext
+ * @param {String} filePath `/a/b.jpg` => `jpg`
+ */
+export declare const getExt: (filePath: string) => string;
+```
+
+## License
+
+Licensed under the MIT License.
diff --git a/ExpressAPI/src/shared/helpers/recursiveFilesStats/RecursiveFilesStats.ts b/ExpressAPI/src/shared/helpers/recursiveFilesStats/RecursiveFilesStats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a571aaac797de782ee6bc95d33ad9e75ee03b647
--- /dev/null
+++ b/ExpressAPI/src/shared/helpers/recursiveFilesStats/RecursiveFilesStats.ts
@@ -0,0 +1,146 @@
+import fs   from 'node:fs';
+import path from 'node:path';
+
+
+export interface RecursiveReaddirFilesOptions {
+    /**
+     * Ignore files
+     * @example `/\/(node_modules|\.git)/`
+     */
+    ignored?: RegExp;
+    /**
+     * Specifies a list of `glob` patterns that match files to be included in compilation.
+     * @example `/(\.json)$/`
+     */
+    include?: RegExp;
+    /**
+     * Specifies a list of files to be excluded from compilation.
+     * @example `/(package\.json)$/`
+     */
+    exclude?: RegExp;
+    /** Provide filtering methods to filter data. */
+    filter?: (item: IFileDirStat) => boolean;
+    /** Do not give the absolute path but the relative one from the root folder */
+    replacePathByRelativeOne?: boolean;
+    /** Remove stats that are not necessary for transfert */
+    liteStats?: boolean;
+}
+
+
+export interface IFileDirStat extends Partial<fs.Stats> {
+    /**
+     * @example `/a/sum.jpg` => `sum.jpg`
+     */
+    name: string;
+    /**
+     * @example `/basic/src/utils/sum.ts`
+     */
+    path: string;
+    /**
+     * @example `/a/b.jpg` => `jpg`
+     */
+    ext?: string;
+}
+
+
+type Callback = (filepath: string, stat: IFileDirStat) => void;
+
+
+class RecursiveFilesStats {
+    async explore(rootPath: string, options: RecursiveReaddirFilesOptions = {}, callback?: Callback): Promise<IFileDirStat[]> {
+        return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback);
+    }
+
+    private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> {
+        const {
+                  ignored, include, exclude, filter
+              } = options;
+        const filesData = await fs.promises.readdir(rootPath);
+        const fileDir: IFileDirStat[] = filesData.map((file) => ({
+            name: file, path: path.join(rootPath, file)
+        })).filter((item) => {
+            if ( include && include.test(item.path) ) {
+                return true;
+            }
+            if ( exclude && exclude.test(item.path) ) {
+                return false;
+            }
+            if ( ignored ) {
+                return !ignored.test(item.path);
+            }
+            return true;
+        });
+        if ( callback ) {
+            fileDir.map(async (item: IFileDirStat) => {
+                const stat = await this.getStat(item.path, absoluteBasePath, options);
+                if ( stat.isDirectory!() ) {
+                    await this.getFiles(absoluteBasePath, item.path, options, [], callback);
+                }
+                callback(item.path, stat);
+            });
+        } else {
+            await Promise.all(fileDir.map(async (item: IFileDirStat) => {
+                const stat = await this.getStat(item.path, absoluteBasePath, options);
+                if ( stat.isDirectory!() ) {
+                    const arr = await this.getFiles(absoluteBasePath, item.path, options, []);
+                    files = files.concat(arr);
+                } else if ( stat.isFile!() ) {
+                    files.push(stat);
+                }
+            }));
+        }
+        return files.filter((item) => {
+            if ( filter && typeof filter === 'function' ) {
+                return filter(item);
+            }
+            return true;
+        });
+    }
+
+    private async getStat(filepath: string, absoluteRootPath: string, options: RecursiveReaddirFilesOptions): Promise<IFileDirStat> {
+        const stat = (await fs.promises.stat(filepath)) as IFileDirStat;
+        stat.ext = '';
+        if ( stat.isFile!() ) {
+            stat.ext = this.getExt(filepath);
+            stat.name = path.basename(filepath);
+            stat.path = path.resolve(filepath);
+        }
+
+        if ( options.replacePathByRelativeOne && stat.path ) {
+            stat.path = stat.path.replace(absoluteRootPath, '');
+        }
+
+        if ( options.liteStats ) {
+            delete stat.dev;
+            delete stat.nlink;
+            delete stat.uid;
+            delete stat.gid;
+            delete stat.rdev;
+            delete stat.blksize;
+            delete stat.ino;
+            delete stat.blocks;
+            delete stat.atimeMs;
+            delete stat.mtimeMs;
+            delete stat.ctimeMs;
+            delete stat.birthtimeMs;
+            delete stat.atime;
+            //delete stat.mtime;
+            delete stat.ctime;
+            //delete stat.birthtime;
+            //delete stat.mode;
+        }
+
+        return stat;
+    }
+
+    /**
+     * Get ext
+     * @param {String} filePath `/a/b.jpg` => `jpg`
+     */
+    private getExt(filePath: string): string {
+        return path.extname(filePath).replace(/^\./, '').toLowerCase();
+    }
+}
+
+
+export default new RecursiveFilesStats();
diff --git a/ExpressAPI/src/shared/logging/WinstonLogger.ts b/ExpressAPI/src/shared/logging/WinstonLogger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..941e5388ed95b1f281c21f5d2bf2c0c9217ae10a
--- /dev/null
+++ b/ExpressAPI/src/shared/logging/WinstonLogger.ts
@@ -0,0 +1,64 @@
+import winston        from 'winston';
+import SharedConfig   from '../config/SharedConfig';
+import * as Transport from 'winston-transport';
+
+
+const levels = {
+    error: 0,
+    warn : 1,
+    info : 2,
+    http : 3,
+    debug: 4
+};
+
+const colors = {
+    error: 'red',
+    warn : 'orange',
+    info : 'green',
+    http : 'magenta',
+    debug: 'blue'
+};
+winston.addColors(colors);
+
+const format = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format(info => ({
+    ...info,
+    level: info.level.toUpperCase()
+}))(), SharedConfig.production ? winston.format.uncolorize() : winston.format.colorize({ all: true }), winston.format.prettyPrint(), winston.format.errors({ stack: true }), winston.format.align(), winston.format.printf((info) => `[${ info.timestamp }] (${ process.pid }) ${ info.level } ${ info.message } ${ info.metadata ? `\n${ JSON.stringify(info.metadata) }` : '' } ${ info.stack ? `\n${ info.stack }` : '' } `));
+
+const commonTransportOptions = {
+    handleRejections: true,
+    handleExceptions: true
+};
+
+let transports: Array<Transport> = [ new winston.transports.Console({
+                                                                        ...commonTransportOptions,
+                                                                        level: 'debug'
+                                                                    }) ];
+
+if ( SharedConfig.production ) {
+    const commonFileOptions = {
+        ...commonTransportOptions,
+        maxsize : 5242880, // 5MB
+        maxFiles: 100,
+        tailable: true
+    };
+
+    transports = transports.concat([ new winston.transports.File({
+                                                                     ...commonFileOptions,
+                                                                     filename: `${ SharedConfig.logsFolder }/error.log`,
+                                                                     level   : 'error'
+                                                                 }), new winston.transports.File({
+                                                                                                     ...commonFileOptions,
+                                                                                                     filename: `${ SharedConfig.logsFolder }/all.log`,
+                                                                                                     level   : 'debug'
+                                                                                                 }) ]);
+}
+
+const logger = winston.createLogger({
+                                        levels,
+                                        format,
+                                        transports,
+                                        exitOnError: false
+                                    });
+
+export default logger;
diff --git a/ExpressAPI/src/shared/managers/SharedGitlabManager.ts b/ExpressAPI/src/shared/managers/SharedGitlabManager.ts
new file mode 100644
index 0000000000000000000000000000000000000000..68ff699a036044872cb7f33ead08782afbbdb830
--- /dev/null
+++ b/ExpressAPI/src/shared/managers/SharedGitlabManager.ts
@@ -0,0 +1,38 @@
+import axios          from 'axios';
+import GitlabPipeline from '../types/Gitlab/GitlabPipeline';
+import GitlabRoute    from '../types/Gitlab/GitlabRoute';
+import SharedConfig   from '../config/SharedConfig';
+import GitlabToken    from '../types/Gitlab/GitlabToken';
+
+
+class GitlabManager {
+    private getApiUrl(route: GitlabRoute): string {
+        return `${ SharedConfig.gitlab.apiURL }${ route }`;
+    }
+
+    async getTokens(codeOrRefresh: string, isRefresh: boolean = false, clientSecret: string = ''): Promise<GitlabToken> {
+        const response = await axios.post<GitlabToken>(SharedConfig.login.gitlab.url.token, {
+            client_id    : SharedConfig.login.gitlab.client.id,
+            client_secret: clientSecret,
+            grant_type   : isRefresh ? 'refresh_token' : 'authorization_code',
+            refresh_token: codeOrRefresh,
+            code         : codeOrRefresh,
+            redirect_uri : SharedConfig.login.gitlab.url.redirect
+        });
+
+        return response.data;
+    }
+
+    async getRepositoryPipelines(repoId: number, branch: string = 'main'): Promise<Array<GitlabPipeline>> {
+        const response = await axios.get<Array<GitlabPipeline>>(this.getApiUrl(GitlabRoute.REPOSITORY_PIPELINES).replace('{{id}}', String(repoId)), {
+            params: {
+                ref: branch
+            }
+        });
+
+        return response.data;
+    }
+}
+
+
+export default new GitlabManager();
diff --git a/ExpressAPI/src/shared/types/Dojo/AssignmentCheckerError.ts b/ExpressAPI/src/shared/types/Dojo/AssignmentCheckerError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34df2a8d771aad43750bb78ce490bdc3202f1022
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/AssignmentCheckerError.ts
@@ -0,0 +1,23 @@
+enum ExerciseCheckerError {
+    DOCKER_DAEMON_NOT_RUNNING       = 200,
+    REQUIRED_FILES_MISSING          = 201,
+    ASSIGNMENT_FILE_SCHEMA_ERROR    = 202,
+    IMMUTABLE_PATH_NOT_FOUND        = 203,
+    IMMUTABLE_PATH_IS_DIRECTORY     = 204,
+    IMMUTABLE_PATH_IS_NOT_DIRECTORY = 205,
+    COMPOSE_FILE_YAML_ERROR         = 206,
+    COMPOSE_FILE_SCHEMA_ERROR       = 207,
+    COMPOSE_FILE_CONTAINER_MISSING  = 208,
+    COMPOSE_FILE_VOLUME_MISSING     = 209,
+    DOCKERFILE_NOT_FOUND            = 210,
+    COMPOSE_RUN_SUCCESSFULLY        = 211, // Yes, this is an error
+}
+
+
+/**
+ * Codes that are unusable for historic reasons:
+ * None
+ */
+
+
+export default ExerciseCheckerError;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/AssignmentFile.ts b/ExpressAPI/src/shared/types/Dojo/AssignmentFile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df09d53ae249bf3614debd251630eb10e96a5c0b
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/AssignmentFile.ts
@@ -0,0 +1,19 @@
+import ImmutableFileDescriptor from './ImmutableFileDescriptor';
+import { z }                   from 'zod';
+
+
+const AssignmentFile = z.object({
+                                    dojoAssignmentVersion: z.number(),
+                                    version              : z.number(),
+                                    immutable            : z.array(ImmutableFileDescriptor.transform(value => value as ImmutableFileDescriptor)),
+                                    result               : z.object({
+                                                                        container: z.string(),
+                                                                        volume   : z.string().optional()
+                                                                    })
+                                }).strict();
+
+
+type AssignmentFile = z.infer<typeof AssignmentFile>;
+
+
+export default AssignmentFile;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/DojoBackendResponse.ts b/ExpressAPI/src/shared/types/Dojo/DojoBackendResponse.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c6eed91a45e42e0ff784baedeed2c76833236d3
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/DojoBackendResponse.ts
@@ -0,0 +1,10 @@
+interface DojoBackendResponse<T> {
+    timestamp: string;
+    code: number;
+    description: string;
+    sessionToken: string | null;
+    data: T;
+}
+
+
+export default DojoBackendResponse;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/DojoStatusCode.ts b/ExpressAPI/src/shared/types/Dojo/DojoStatusCode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fba7945ae34850d47427001b702107e2c561de32
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/DojoStatusCode.ts
@@ -0,0 +1,16 @@
+enum DojoStatusCode {
+    LOGIN_FAILED                        = 1,
+    REFRESH_TOKENS_FAILED               = 2,
+    CLIENT_NOT_SUPPORTED                = 100,
+    CLIENT_VERSION_NOT_SUPPORTED        = 110,
+    ASSIGNMENT_PUBLISH_NO_PIPELINE      = 200,
+    ASSIGNMENT_PUBLISH_PIPELINE_FAILED  = 201,
+    ASSIGNMENT_CREATION_GITLAB_ERROR    = 202,
+    ASSIGNMENT_CREATION_INTERNAL_ERROR  = 203,
+    EXERCISE_CREATION_GITLAB_ERROR      = 302,
+    EXERCISE_CREATION_INTERNAL_ERROR    = 303,
+    MAX_EXERCISE_PER_ASSIGNMENT_REACHED = 304
+}
+
+
+export default DojoStatusCode;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/ExerciseCheckerError.ts b/ExpressAPI/src/shared/types/Dojo/ExerciseCheckerError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..45810d0c9e34dcdb0c37fcf64525bcad29edbbcb
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/ExerciseCheckerError.ts
@@ -0,0 +1,19 @@
+enum ExerciseCheckerError {
+    EXERCISE_ASSIGNMENT_GET_ERROR          = 200,
+    DOCKER_COMPOSE_RUN_ERROR               = 201,
+    DOCKER_COMPOSE_LOGS_ERROR              = 202,
+    DOCKER_COMPOSE_DOWN_ERROR              = 203,
+    EXERCISE_RESULTS_FOLDER_TOO_BIG        = 204,
+    EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID = 206,
+    UPLOAD                                 = 207,
+    DOCKER_COMPOSE_REMOVE_DANGLING_ERROR   = 208
+}
+
+
+/**
+ * Codes that are unusable for historic reasons:
+ * - 205: EXERCISE_RESULTS_FILE_NOT_FOUND => From the version 2.2.0 this error is not possible anymore because the results file is now optional
+ */
+
+
+export default ExerciseCheckerError;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/ExerciseResultsFile.ts b/ExpressAPI/src/shared/types/Dojo/ExerciseResultsFile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..135a532d11623c74389f049ec83da16a2c5988f2
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/ExerciseResultsFile.ts
@@ -0,0 +1,39 @@
+import Icon  from '../Icon';
+import { z } from 'zod';
+
+
+const ExerciseResultsFile = z.object({
+                                         success: z.boolean().optional(),
+
+                                         containerExitCode: z.number().optional(),
+
+                                         successfulTests: z.number().optional(),
+                                         failedTests    : z.number().optional(),
+
+                                         successfulTestsList: z.array(z.string()).optional(),
+                                         failedTestsList    : z.array(z.string()).optional(),
+
+                                         otherInformations: z.array(z.object({
+                                                                                 name               : z.string(),
+                                                                                 description        : z.string().optional(),
+                                                                                 icon               : z.enum(Object.keys(Icon) as [ firstKey: string, ...otherKeys: Array<string> ]).optional(),
+                                                                                 itemsOrInformations: z.union([ z.array(z.string()), z.string() ])
+                                                                             }))
+                                         .optional()
+                                     }).strict().transform(value => {
+    if ( value.successfulTests === undefined && value.successfulTestsList !== undefined ) {
+        value.successfulTests = value.successfulTestsList.length;
+    }
+
+    if ( value.failedTests === undefined && value.failedTestsList !== undefined ) {
+        value.failedTests = value.failedTestsList.length;
+    }
+
+    return value;
+});
+
+
+type ExerciseResultsFile = z.infer<typeof ExerciseResultsFile>;
+
+
+export default ExerciseResultsFile;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Dojo/ImmutableFileDescriptor.ts b/ExpressAPI/src/shared/types/Dojo/ImmutableFileDescriptor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..27f66a1104340bc404b6788a88b06b776df8f5e4
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Dojo/ImmutableFileDescriptor.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+
+const ImmutableFileDescriptor = z.object({
+                                             description: z.string().optional(),
+                                             path       : z.string(),
+                                             isDirectory: z.boolean().optional()
+                                         });
+
+
+type ImmutableFileDescriptor = z.infer<typeof ImmutableFileDescriptor>;
+
+
+export default ImmutableFileDescriptor;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabAccessLevel.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabAccessLevel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be06ffd009103feb9f30a4e18250c2fcf978527b
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabAccessLevel.ts
@@ -0,0 +1,11 @@
+enum GitlabAccessLevel {
+    GUEST      = 10,
+    REPORTER   = 20,
+    DEVELOPER  = 30,
+    MAINTAINER = 40,
+    OWNER      = 50,
+    ADMIN      = 60
+}
+
+
+export default GitlabAccessLevel;
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabCommit.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabCommit.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f94c2e431b220b4247e0dc3bf0222fba14f2a1c
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabCommit.ts
@@ -0,0 +1,17 @@
+interface GitlabCommit {
+    id: string;
+    short_id: string;
+    created_at: string;
+    parent_ids: Array<string>;
+    title: string;
+    message: string;
+    author_name: string;
+    author_email: string;
+    authored_date: string;
+    committer_name: string;
+    committer_email: string;
+    committed_date: string;
+}
+
+
+export default GitlabCommit;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabFile.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabFile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05205d4f1e28d5d04e60ae304acb02f3ddcb0d02
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabFile.ts
@@ -0,0 +1,16 @@
+interface GitlabFile {
+    file_name: string,
+    file_path: string,
+    size: number,
+    encoding: string,
+    content_sha256: string,
+    ref: string,
+    blob_id: string,
+    commit_id: string,
+    last_commit_id: string,
+    execute_filemode: boolean,
+    content: string,
+}
+
+
+export default GitlabFile;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabGroup.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabGroup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..812f8838b5e00eb50cea0a282e3e26dc20344fef
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabGroup.ts
@@ -0,0 +1,10 @@
+interface GitlabGroup {
+    group_id: number,
+    group_name: string,
+    group_full_path: string,
+    group_access_level: number,
+    expires_at: string
+}
+
+
+export default GitlabGroup;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabMember.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabMember.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c7c7163f73104221de918af4ceb09d4a55c98e2
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabMember.ts
@@ -0,0 +1,12 @@
+import GitlabUser from './GitlabUser';
+
+
+interface GitlabMember extends GitlabUser {
+    access_level: number,
+    created_at: string,
+    created_by: GitlabUser,
+    expires_at: string | null
+}
+
+
+export default GitlabMember;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabMilestone.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabMilestone.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7285cd9ee3683aeaf4ebc1ea85ab778af1e970c
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabMilestone.ts
@@ -0,0 +1,19 @@
+interface GitlabMilestone {
+    id: number;
+    iid: number;
+    project_id: number;
+    title: string;
+    description: string;
+    state: string;
+    created_at: string;
+    updated_at: string;
+    due_date: string;
+    start_date: string;
+    web_url: string;
+    issue_stats: {
+        total: number; closed: number;
+    };
+}
+
+
+export default GitlabMilestone;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabNamespace.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabNamespace.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4d892afb58d81138a6b204124fc5b2ed6526eeca
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabNamespace.ts
@@ -0,0 +1,13 @@
+interface GitlabNamespace {
+    id: number,
+    name: string,
+    path: string,
+    kind: string,
+    full_path: string,
+    parent_id: number,
+    avatar_url: string,
+    web_url: string
+}
+
+
+export default GitlabNamespace;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabPipeline.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabPipeline.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1ee75b643b458b1f0c924f5d47f7ae0f0f88ddb0
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabPipeline.ts
@@ -0,0 +1,31 @@
+import GitlabPipelineStatus from './GitlabPipelineStatus';
+import GitlabPipelineSource from './GitlabPipelineSource';
+import GitlabUser           from './GitlabUser';
+
+
+interface GitlabPipeline {
+    id: number,
+    iid: number,
+    project_id: number,
+    status: GitlabPipelineStatus,
+    source: GitlabPipelineSource,
+    ref: string,
+    sha: string,
+    before_sha: string,
+    tag: boolean,
+    name: string,
+    yaml_errors: string | null,
+    user: GitlabUser,
+    web_url: string,
+    created_at: string,
+    updated_at: string,
+    started_at: string | null,
+    finished_at: string | null,
+    committed_at: string | null,
+    duration: number | null,
+    queued_duration: number | null,
+    coverage: string | null,
+}
+
+
+export default GitlabPipeline;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineSource.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineSource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33253b07ded1850dfcce309971e84836e417a615
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineSource.ts
@@ -0,0 +1,19 @@
+enum GitlabPipelineSource {
+    PUSH                      = 'push',
+    WEB                       = 'web',
+    TRIGGER                   = 'trigger',
+    SCHEDULE                  = 'schedule',
+    API                       = 'api',
+    EXTERNAL                  = 'external',
+    PIPELINE                  = 'pipeline',
+    CHAT                      = 'chat',
+    WEBIDE                    = 'webide',
+    MERGE_REQUEST             = 'merge_request_event',
+    EXTERNAL_PULL_REQUEST     = 'external_pull_request_event',
+    PARENT_PIPELINE           = 'parent_pipeline',
+    ON_DEMAND_DAST_SCAN       = 'ondemand_dast_scan',
+    ON_DEMAND_DAST_VALIDATION = 'ondemand_dast_validation',
+}
+
+
+export default GitlabPipelineSource;
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineStatus.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineStatus.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd6d7b268b951785fccfe11b6a82f3b6376b566a
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabPipelineStatus.ts
@@ -0,0 +1,16 @@
+enum GitlabPipelineStatus {
+    CREATED              = 'created',
+    WAITING_FOR_RESOURCE = 'waiting_for_resource',
+    PREPARING            = 'preparing',
+    PENDING              = 'pending',
+    RUNNING              = 'running',
+    SUCCESS              = 'success',
+    FAILED               = 'failed',
+    CANCELED             = 'canceled',
+    SKIPPED              = 'skipped',
+    MANUAL               = 'manual',
+    SCHEDULED            = 'scheduled'
+}
+
+
+export default GitlabPipelineStatus;
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabProfile.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabProfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa7506dbddcf7051a29c118d69fc26d1f7fb55bb
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabProfile.ts
@@ -0,0 +1,40 @@
+import GitlabUser from './GitlabUser';
+
+
+interface GitlabProfile extends GitlabUser {
+    created_at: string,
+    bio: string,
+    location: string,
+    public_email: string,
+    skype: string,
+    linkedin: string,
+    twitter: string,
+    discord: string,
+    website_url: string,
+    organization: string,
+    job_title: string,
+    pronouns: string,
+    bot: boolean,
+    work_information: string,
+    local_time: string,
+    last_sign_in_at: string,
+    confirmed_at: string,
+    last_activity_on: string,
+    email: string,
+    theme_id: number,
+    color_scheme_id: number,
+    projects_limit: number,
+    current_sign_in_at: string,
+    identities: Array<{
+        provider: string, extern_uid: string
+    }>,
+    can_create_group: boolean,
+    can_create_project: boolean,
+    two_factor_enabled: boolean,
+    external: boolean,
+    private_profile: boolean,
+    commit_email: string
+}
+
+
+export default GitlabProfile;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabRelease.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabRelease.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7c68d770e4e88c7acd2f6f6eb1305329c42f61d
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabRelease.ts
@@ -0,0 +1,29 @@
+import GitlabUser      from './GitlabUser';
+import GitlabCommit    from './GitlabCommit';
+import GitlabMilestone from './GitlabMilestone';
+
+
+interface GitlabRelease {
+    tag_name: string;
+    description: string;
+    created_at: string;
+    released_at: string;
+    author: GitlabUser;
+    commit: GitlabCommit;
+    milestones: Array<GitlabMilestone>;
+    commit_path: string;
+    tag_path: string;
+    assets: {
+        count: number; sources: Array<{
+            format: string; url: string;
+        }>; links: Array<{
+            id: number; name: string; url: string; link_type: string;
+        }>; evidence_file_path: string;
+    };
+    evidences: Array<{
+        sha: string; filepath: string; collected_at: string;
+    }>;
+}
+
+
+export default GitlabRelease; 
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabRepository.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabRepository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0aa260287cc95030231761b2781a31a1b203f22b
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabRepository.ts
@@ -0,0 +1,106 @@
+import GitlabGroup     from './GitlabGroup';
+import GitlabNamespace from './GitlabNamespace';
+
+
+interface GitlabRepository {
+    id: number,
+    description: string,
+    name: string,
+    name_with_namespace: string,
+    path: string,
+    path_with_namespace: string,
+    created_at: string,
+    default_branch: string,
+    tag_list: Array<string>,
+    topics: Array<string>,
+    ssh_url_to_repo: string,
+    http_url_to_repo: string,
+    web_url: string,
+    readme_url: string,
+    forks_count: number,
+    avatar_url: string,
+    star_count: number,
+    last_activity_at: string,
+    namespace: GitlabNamespace,
+    _links: {
+        self: string, issues: string, merge_requests: string, repo_branches: string, labels: string, events: string, members: string, cluster_agents: string
+    },
+    packages_enabled: boolean,
+    empty_repo: boolean,
+    archived: boolean,
+    visibility: string,
+    resolve_outdated_diff_discussions: boolean,
+    container_expiration_policy: {
+        cadence: string, enabled: boolean, keep_n: number, older_than: string, name_regex: string, name_regex_keep: string, next_run_at: string
+    },
+    issues_enabled: boolean,
+    merge_requests_enabled: boolean,
+    wiki_enabled: boolean,
+    jobs_enabled: boolean,
+    snippets_enabled: boolean,
+    container_registry_enabled: boolean,
+    service_desk_enabled: boolean,
+    service_desk_address: string,
+    can_create_merge_request_in: boolean,
+    issues_access_level: string,
+    repository_access_level: string,
+    merge_requests_access_level: string,
+    forking_access_level: string,
+    wiki_access_level: string,
+    builds_access_level: string,
+    snippets_access_level: string,
+    pages_access_level: string,
+    operations_access_level: string,
+    analytics_access_level: string,
+    container_registry_access_level: string,
+    security_and_compliance_access_level: string,
+    releases_access_level: string,
+    environments_access_level: string,
+    feature_flags_access_level: string,
+    infrastructure_access_level: string,
+    monitor_access_level: string,
+    emails_disabled: boolean,
+    shared_runners_enabled: boolean,
+    lfs_enabled: boolean,
+    creator_id: number,
+    import_url: string,
+    import_type: string,
+    import_status: string,
+    import_error: string,
+    open_issues_count: number,
+    runners_token: string,
+    ci_default_git_depth: number,
+    ci_forward_deployment_enabled: boolean,
+    ci_job_token_scope_enabled: boolean,
+    ci_separated_caches: boolean,
+    ci_opt_in_jwt: boolean,
+    ci_allow_fork_pipelines_to_run_in_parent_project: boolean,
+    public_jobs: boolean,
+    build_git_strategy: string,
+    build_timeout: number,
+    auto_cancel_pending_pipelines: string,
+    ci_config_path: string,
+    shared_with_groups: Array<GitlabGroup>,
+    only_allow_merge_if_pipeline_succeeds: boolean,
+    allow_merge_on_skipped_pipeline: boolean,
+    restrict_user_defined_variables: boolean,
+    request_access_enabled: boolean,
+    only_allow_merge_if_all_discussions_are_resolved: boolean,
+    remove_source_branch_after_merge: boolean,
+    printing_merge_request_link_enabled: boolean,
+    merge_method: string,
+    squash_option: string,
+    enforce_auth_checks_on_uploads: boolean,
+    suggestion_commit_message: string,
+    merge_commit_template: string,
+    squash_commit_template: string,
+    issue_branch_template: string,
+    auto_devops_enabled: boolean,
+    auto_devops_deploy_strategy: string,
+    autoclose_referenced_issues: boolean,
+    keep_latest_artifact: boolean,
+    runner_token_expiration_interval: number,
+}
+
+
+export default GitlabRepository;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabRoute.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabRoute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a1da89cd7b402d4cf776d85f6976a668ed7ba7b
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabRoute.ts
@@ -0,0 +1,23 @@
+enum GitlabRoute {
+    NOTIFICATION_SETTINGS       = '/notification_settings',
+    PROFILE_GET                 = '/user',
+    USERS_GET                   = '/users',
+    REPOSITORY_GET              = '/projects/{{id}}',
+    REPOSITORY_CREATE           = '/projects', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+    REPOSITORY_DELETE           = '/projects/{{id}}', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+    REPOSITORY_EDIT             = '/projects/{{id}}',
+    REPOSITORY_FORK             = '/projects/{{id}}/fork',
+    REPOSITORY_MEMBER_ADD       = '/projects/{{id}}/members',
+    REPOSITORY_MEMBERS_GET      = '/projects/{{id}}/members/all',
+    REPOSITORY_RELEASES_GET     = '/projects/{{id}}/releases',
+    REPOSITORY_BADGES_ADD       = '/projects/{{id}}/badges',
+    REPOSITORY_VARIABLES_ADD    = '/projects/{{id}}/variables',
+    REPOSITORY_BRANCHES_PROTECT = '/projects/{{id}}/protected_branches',
+    REPOSITORY_TREE             = '/projects/{{id}}/repository/tree',
+    REPOSITORY_FILE             = '/projects/{{id}}/repository/files/{{filePath}}',
+    REPOSITORY_PIPELINES        = '/projects/{{id}}/pipelines',
+    REPOSITORY_MEMBER_DELETE    = '/projects/{{id}}/members/{{user_id}}',
+}
+
+
+export default GitlabRoute;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabToken.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabToken.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8c647e6285cf626b155d7ff93beb0dd2c7f9d52
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabToken.ts
@@ -0,0 +1,11 @@
+interface GitlabToken {
+    access_token: string;
+    token_type: string;
+    expires_in: number;
+    refresh_token: string;
+    scope: string;
+    created_at: number;
+}
+
+
+export default GitlabToken;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFile.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b2cf67ecb1d636838ff2e48cbc6ce62e11248476
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFile.ts
@@ -0,0 +1,13 @@
+import GitlabTreeFileType from './GitlabTreeFileType';
+
+
+interface GitlabTreeFile {
+    id: number,
+    name: string,
+    type: GitlabTreeFileType,
+    path: string,
+    mode: string
+}
+
+
+export default GitlabTreeFile;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFileType.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFileType.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eead9b931715ec73fa158513775419dfb14ee538
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabTreeFileType.ts
@@ -0,0 +1,8 @@
+enum GitlabTreeFileType {
+    TREE   = 'tree',
+    BLOB   = 'blob',
+    COMMIT = 'commit'
+}
+
+
+export default GitlabTreeFileType;
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabUser.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabUser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bbb759264764020b1dd80cca050392371b8e87b7
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabUser.ts
@@ -0,0 +1,11 @@
+interface GitlabUser {
+    id: number,
+    username: string,
+    name: string,
+    state: string,
+    avatar_url: string,
+    web_url: string,
+}
+
+
+export default GitlabUser;
\ No newline at end of file
diff --git a/ExpressAPI/src/shared/types/Gitlab/GitlabVisibility.ts b/ExpressAPI/src/shared/types/Gitlab/GitlabVisibility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..842ff168e274cb6cfb2a39ef7a952cdb5248c1b9
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Gitlab/GitlabVisibility.ts
@@ -0,0 +1,8 @@
+enum GitlabVisibility {
+    PUBLIC   = 'public',
+    INTERNAL = 'internal',
+    PRIVATE  = 'private'
+}
+
+
+export default GitlabVisibility;
diff --git a/ExpressAPI/src/shared/types/Icon.ts b/ExpressAPI/src/shared/types/Icon.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33ee07279853d172f2c975a01f6299b438449175
--- /dev/null
+++ b/ExpressAPI/src/shared/types/Icon.ts
@@ -0,0 +1,16 @@
+const Icon = {
+    CAT_INFO : '▶️',
+    INFO     : 'ℹ️',
+    ERROR    : '⛔️',
+    SUCCESS  : '✅',
+    FAILURE  : '❌',
+    VOMIT    : '🤮',
+    YUCK     : '🤢',
+    WELL_DONE: '👍',
+    NULL     : '',
+    NONE     : '',
+    BADMINTON: '🏸'
+} as { [index: string]: string };
+
+
+export default Icon;
\ No newline at end of file
diff --git a/ExpressAPI/src/types/express/index.d.ts b/ExpressAPI/src/types/express/index.d.ts
index 3e2f31a6ff392ce42ac23939b06ba48285d55ddf..336516e6f263398dd418fc32e4df918823bf0138 100644
--- a/ExpressAPI/src/types/express/index.d.ts
+++ b/ExpressAPI/src/types/express/index.d.ts
@@ -1,4 +1,4 @@
-    import { User } from '@prisma/client';
+import { User } from '@prisma/client';
 import Session                  from '../../controllers/Session';
 import { Assignment, Exercise } from '../DatabaseTypes';