diff --git a/.gitignore b/.gitignore
index 43a8d1508996bab2a79fd3dd26f21cbc919a37d3..58df814f697435a0a6134847e04a1cb651dd70ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,9 @@
 aws.xml
 workspace.xml
+.gitlab-ci-local
+Wiki/.idea
+
+ExpressAPI/src/config/Version.ts
 
 ############################ MacOS
 # General
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf73028d9cc14b5cafaac006e5551f9ebb555b0c..3d352420ac80f93a9101b025cceec9e3014b8fb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,20 @@
 **⚠️ Deprecation:**
 -->
 
-## 2.1.0 (?)
+## 2.2.0 (2023-10-16)
+
+### ✨ Feature
+- `results.json` file is now optional (if the teaching staff don't want to provide test details)
+    - The exercise will be considered as valid if the container exit code is 0
+    - The `results.json` file will be construct / completed with the container exit code
+    - The `volume` argument of `dojo_assignment.json` is now optional (if the teaching staff don't want to provide `results.json` file or other files)
+- Client version detection (if the client version is too old, the API will return an error)
+
+### 🔨 Internal / Developers
+- Enhancement in pipelines by splitting them into several files
+
+
+## 2.1.0 (2023-09-29)
 
 ### ✨ Feature
 - Added pipeline badge to exercises
diff --git a/ExpressAPI/.env.vault b/ExpressAPI/.env.vault
index 0726112735c363e4ce411d6dfc3cd512c39e2d99..7e45ae4669f2d45eb5ff0b09d06bfda38a329afb 100644
--- a/ExpressAPI/.env.vault
+++ b/ExpressAPI/.env.vault
@@ -4,8 +4,8 @@
 #/--------------------------------------------------/
 
 # development
-DOTENV_VAULT_DEVELOPMENT="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"
+DOTENV_VAULT_DEVELOPMENT="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"
 
 # production
-DOTENV_VAULT_PRODUCTION="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"
+DOTENV_VAULT_PRODUCTION="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"
 
diff --git a/ExpressAPI/assets/assignment_gitlab_ci.yml b/ExpressAPI/assets/assignment_gitlab_ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ad20a046e037406773064a11f934ebcf69e42f77
--- /dev/null
+++ b/ExpressAPI/assets/assignment_gitlab_ci.yml
@@ -0,0 +1,26 @@
+###################################################################################################################
+# DO NOT MODIFY THIS FILE
+# This file is the ci/cd pipeline that will be used to test your assignment
+###################################################################################################################
+
+variables:
+    GIT_SUBMODULE_STRATEGY: recursive
+    GIT_SUBMODULE_FORCE_HTTPS: "true"
+    DOCKER_HOST: tcp://docker:2375
+    DOCKER_TLS_CERTDIR:
+    DOCKER_DRIVER: overlay2
+
+stages:
+    - dojo
+
+dojo:
+    stage: dojo
+    tags:
+        - dojo_assignment
+    services:
+        - docker:dind
+    image:
+        name: dojohesso/dojo_assignment_checker:latest
+    script:
+        - dojo_assignment_checker
+    allow_failure: false
\ No newline at end of file
diff --git a/ExpressAPI/package-lock.json b/ExpressAPI/package-lock.json
index 55b1690c568184027ff87bddd32cada603203efe..8f963c7eac7601ac218372bff36cd4a480d1dc0e 100644
--- a/ExpressAPI/package-lock.json
+++ b/ExpressAPI/package-lock.json
@@ -1,12 +1,13 @@
 {
     "name": "dojo_backend_api",
-    "version": "2.1.0",
+    "version": "2.2.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "dojo_backend_api",
-            "version": "2.1.0",
+            "version": "2.2.0",
+            "license": "AGPLv3",
             "dependencies": {
                 "@prisma/client": "^5.1.1",
                 "ajv": "^8.12.0",
@@ -15,6 +16,7 @@
                 "compression": "^1.7.4",
                 "cors": "^2.8.5",
                 "dotenv": "^16.3.1",
+                "dotenv-expand": "^10.0.0",
                 "express": "^4.18.2",
                 "express-validator": "^7.0.1",
                 "form-data": "^4.0.0",
@@ -28,6 +30,7 @@
                 "mysql": "^2.18.1",
                 "node": "^20.5.0",
                 "parse-link-header": "^2.0.0",
+                "semver": "^7.5.4",
                 "tar-stream": "^3.1.6",
                 "uuid": "^9.0.0",
                 "winston": "^3.8.2"
@@ -42,9 +45,11 @@
                 "@types/multer": "^1.4.7",
                 "@types/node": "^20.4.7",
                 "@types/parse-link-header": "^2.0.1",
+                "@types/semver": "^7.5.3",
                 "@types/tar-stream": "^2.2.2",
                 "@types/uuid": "^9.0.2",
                 "dotenv-vault": "^1.25.0",
+                "genversion": "^3.1.1",
                 "nodemon": "^3.0.1",
                 "npm": "^9.8.1",
                 "prisma": "^5.1.1",
@@ -198,36 +203,12 @@
                 }
             }
         },
-        "node_modules/@oclif/core/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/@oclif/core/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
             "dev": true
         },
-        "node_modules/@oclif/core/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/@oclif/linewrap": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz",
@@ -235,9 +216,9 @@
             "dev": true
         },
         "node_modules/@oclif/plugin-help": {
-            "version": "5.2.19",
-            "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.2.19.tgz",
-            "integrity": "sha512-gf6/dFtzMJ8RA4ovlBCBGJsZsd4jPXhYWJho+Gh6KmA+Ev9LupoExbE0qT+a2uHJyHEvIg4uX/MBW3qdERD/8g==",
+            "version": "5.2.20",
+            "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.2.20.tgz",
+            "integrity": "sha512-u+GXX/KAGL9S10LxAwNUaWdzbEBARJ92ogmM7g3gDVud2HioCmvWQCDohNRVZ9GYV9oKwZ/M8xwd6a1d95rEKQ==",
             "dev": true,
             "dependencies": {
                 "@oclif/core": "^2.15.0"
@@ -302,40 +283,16 @@
                 }
             }
         },
-        "node_modules/@oclif/plugin-help/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/@oclif/plugin-help/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
             "dev": true
         },
-        "node_modules/@oclif/plugin-help/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/@oclif/plugin-not-found": {
-            "version": "2.4.1",
-            "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-2.4.1.tgz",
-            "integrity": "sha512-LqW7qpw5Q8ploRiup2jEIMQJXcxHP1tpwj45GApKQMe7GRdGdRdjBT9Tu+U2tdEgMqgMplAIhOsYCx2nc2nMSw==",
+            "version": "2.4.3",
+            "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-2.4.3.tgz",
+            "integrity": "sha512-nIyaR4y692frwh7wIHZ3fb+2L6XEecQwRDIb4zbEam0TvaVmBQWZoColQyWA84ljFBPZ8XWiQyTz+ixSwdRkqg==",
             "dev": true,
             "dependencies": {
                 "@oclif/core": "^2.15.0",
@@ -402,40 +359,16 @@
                 }
             }
         },
-        "node_modules/@oclif/plugin-not-found/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/@oclif/plugin-not-found/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
             "dev": true
         },
-        "node_modules/@oclif/plugin-not-found/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/@oclif/plugin-update": {
-            "version": "3.2.3",
-            "resolved": "https://registry.npmjs.org/@oclif/plugin-update/-/plugin-update-3.2.3.tgz",
-            "integrity": "sha512-JVKwp4ysG9GU4RmG59MZYMunz8onRI+wEQzJThyYkUFd0VfZviYt2FHsyoNtxi30l0tInC8APgKp1pCCO4e+FQ==",
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/@oclif/plugin-update/-/plugin-update-3.2.4.tgz",
+            "integrity": "sha512-41G7NTKND+yTpb8LHlvlMIcNoaEUIIJuEwju9igL+ME/pN/53opeXgFV2IjjeFiexXj50OfesY9OQ6lqOZHw+g==",
             "dev": true,
             "dependencies": {
                 "@oclif/core": "^2.11.8",
@@ -511,40 +444,16 @@
                 }
             }
         },
-        "node_modules/@oclif/plugin-update/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/@oclif/plugin-update/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
             "dev": true
         },
-        "node_modules/@oclif/plugin-update/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/@oclif/plugin-warn-if-update-available": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.1.0.tgz",
-            "integrity": "sha512-liTWd/qSIqALsikr88CAB9o2xGFt0LdT5REbhxtrx16/trRmkxQ+0RHK1FieGZAzEENx/4D3YcC/Y67a0uyO0g==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.1.1.tgz",
+            "integrity": "sha512-y7eSzT6R5bmTIJbiMMXgOlbBpcWXGlVhNeQJBLBCCy1+90Wbjyqf6uvY0i2WcO4sh/THTJ20qCW80j3XUlgDTA==",
             "dev": true,
             "dependencies": {
                 "@oclif/core": "^2.15.0",
@@ -614,52 +523,28 @@
                 }
             }
         },
-        "node_modules/@oclif/plugin-warn-if-update-available/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/@oclif/plugin-warn-if-update-available/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
             "dev": true
         },
-        "node_modules/@oclif/plugin-warn-if-update-available/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/@oclif/screen": {
-            "version": "3.0.6",
-            "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.6.tgz",
-            "integrity": "sha512-nEv7dFPxCrWrvK6dQ8zya0/Kb54EXVcwIKV9capjSa89ZDoOo+qH0YSo4/eQVECXgW3eUvgKLDIcIt62YBk0HA==",
+            "version": "3.0.7",
+            "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.7.tgz",
+            "integrity": "sha512-jQBPHcMh5rcIPKdqA6xlzioLOmkaVnjg2MVyjMzBKV8hDhLWNSiZqx7NAWXpP70v2LFvGdVoV8BSbK9iID3eHg==",
             "dev": true,
             "engines": {
                 "node": ">=12.0.0"
             }
         },
         "node_modules/@prisma/client": {
-            "version": "5.1.1",
-            "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.1.1.tgz",
-            "integrity": "sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==",
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.3.1.tgz",
+            "integrity": "sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==",
             "hasInstallScript": true,
             "dependencies": {
-                "@prisma/engines-version": "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e"
+                "@prisma/engines-version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59"
             },
             "engines": {
                 "node": ">=16.13"
@@ -674,16 +559,16 @@
             }
         },
         "node_modules/@prisma/engines": {
-            "version": "5.1.1",
-            "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.1.1.tgz",
-            "integrity": "sha512-NV/4nVNWFZSJCCIA3HIFJbbDKO/NARc9ej0tX5S9k2EVbkrFJC4Xt9b0u4rNZWL4V+F5LAjvta8vzEUw0rw+HA==",
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.3.1.tgz",
+            "integrity": "sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==",
             "devOptional": true,
             "hasInstallScript": true
         },
         "node_modules/@prisma/engines-version": {
-            "version": "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e",
-            "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e.tgz",
-            "integrity": "sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ=="
+            "version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59",
+            "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz",
+            "integrity": "sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w=="
         },
         "node_modules/@tsconfig/node10": {
             "version": "1.0.9",
@@ -710,15 +595,15 @@
             "dev": true
         },
         "node_modules/@types/bcryptjs": {
-            "version": "2.4.2",
-            "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
-            "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==",
+            "version": "2.4.4",
+            "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.4.tgz",
+            "integrity": "sha512-9wlJI7k5gRyJEC4yrV7DubzNQFTPiykYxUA6lBtsk5NlOfW9oWLJ1HdIA4YtE+6C3i3mTpDQQEosJ2rVZfBWnw==",
             "dev": true
         },
         "node_modules/@types/body-parser": {
-            "version": "1.19.2",
-            "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
-            "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+            "version": "1.19.3",
+            "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz",
+            "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==",
             "dev": true,
             "dependencies": {
                 "@types/connect": "*",
@@ -726,45 +611,45 @@
             }
         },
         "node_modules/@types/cli-progress": {
-            "version": "3.11.2",
-            "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.2.tgz",
-            "integrity": "sha512-Yt/8rEJalfa9ve2SbfQnwFHrc9QF52JIZYHW3FDaTMpkCvnns26ueKiPHDxyJ0CS//IqjMINTx7R5Xa7k7uFHQ==",
+            "version": "3.11.3",
+            "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz",
+            "integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/compression": {
-            "version": "1.7.2",
-            "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz",
-            "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==",
+            "version": "1.7.3",
+            "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.3.tgz",
+            "integrity": "sha512-rKquEGjebqizyHNMOpaE/4FdYR5VQiWFeesqYfvJU0seSEyB4625UGhNOO/qIkH10S3wftiV7oefc8WdLZ/gCQ==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*"
             }
         },
         "node_modules/@types/connect": {
-            "version": "3.4.35",
-            "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
-            "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+            "version": "3.4.36",
+            "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz",
+            "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/cors": {
-            "version": "2.8.13",
-            "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
-            "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
+            "version": "2.8.14",
+            "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz",
+            "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/express": {
-            "version": "4.17.17",
-            "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
-            "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
+            "version": "4.17.18",
+            "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz",
+            "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==",
             "dev": true,
             "dependencies": {
                 "@types/body-parser": "*",
@@ -774,9 +659,9 @@
             }
         },
         "node_modules/@types/express-serve-static-core": {
-            "version": "4.17.35",
-            "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
-            "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
+            "version": "4.17.37",
+            "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz",
+            "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*",
@@ -785,43 +670,49 @@
                 "@types/send": "*"
             }
         },
+        "node_modules/@types/http-errors": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz",
+            "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==",
+            "dev": true
+        },
         "node_modules/@types/jsonwebtoken": {
-            "version": "9.0.2",
-            "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
-            "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
+            "version": "9.0.3",
+            "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+            "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/mime": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
-            "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
+            "version": "1.3.3",
+            "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
+            "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==",
             "dev": true
         },
         "node_modules/@types/morgan": {
-            "version": "1.9.4",
-            "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz",
-            "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==",
+            "version": "1.9.6",
+            "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.6.tgz",
+            "integrity": "sha512-xfKogz5WcKww2DAiVT9zxMgrqQt+Shq8tDVeLT+otoj6dJnkRkyJxMF51mHtUc3JCPKGk5x1EBU0buuGpfftlQ==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/multer": {
-            "version": "1.4.7",
-            "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
-            "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==",
+            "version": "1.4.8",
+            "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.8.tgz",
+            "integrity": "sha512-VMZOW6mnmMMhA5m3fsCdXBwFwC+a+27/8gctNMuQC4f7UtWcF79KAFGoIfKZ4iqrElgWIa3j5vhMJDp0iikQ1g==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*"
             }
         },
         "node_modules/@types/node": {
-            "version": "20.4.7",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz",
-            "integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==",
+            "version": "20.8.2",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
+            "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
             "dev": true
         },
         "node_modules/@types/parse-link-header": {
@@ -831,21 +722,27 @@
             "dev": true
         },
         "node_modules/@types/qs": {
-            "version": "6.9.7",
-            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
-            "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+            "version": "6.9.8",
+            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
+            "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==",
             "dev": true
         },
         "node_modules/@types/range-parser": {
-            "version": "1.2.4",
-            "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
-            "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz",
+            "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==",
+            "dev": true
+        },
+        "node_modules/@types/semver": {
+            "version": "7.5.3",
+            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
+            "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==",
             "dev": true
         },
         "node_modules/@types/send": {
-            "version": "0.17.1",
-            "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
-            "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
+            "version": "0.17.2",
+            "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz",
+            "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==",
             "dev": true,
             "dependencies": {
                 "@types/mime": "^1",
@@ -853,33 +750,34 @@
             }
         },
         "node_modules/@types/serve-static": {
-            "version": "1.15.1",
-            "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
-            "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
+            "version": "1.15.3",
+            "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz",
+            "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==",
             "dev": true,
             "dependencies": {
+                "@types/http-errors": "*",
                 "@types/mime": "*",
                 "@types/node": "*"
             }
         },
         "node_modules/@types/tar-stream": {
-            "version": "2.2.2",
-            "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz",
-            "integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==",
+            "version": "2.2.3",
+            "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.3.tgz",
+            "integrity": "sha512-if3mugZfjVkXOMZdFjIHySxY13r6GXPpyOlsDmLffvyI7tLz9wXE8BFjNivXsvUeyJ1KNlOpfLnag+ISmxgxPw==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
         },
         "node_modules/@types/triple-beam": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz",
-            "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g=="
+            "version": "1.3.3",
+            "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz",
+            "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g=="
         },
         "node_modules/@types/uuid": {
-            "version": "9.0.2",
-            "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz",
-            "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==",
+            "version": "9.0.4",
+            "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz",
+            "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==",
             "dev": true
         },
         "node_modules/abbrev": {
@@ -901,9 +799,9 @@
             }
         },
         "node_modules/acorn": {
-            "version": "8.8.2",
-            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-            "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+            "version": "8.10.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+            "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
             "dev": true,
             "bin": {
                 "acorn": "bin/acorn"
@@ -975,24 +873,6 @@
                 "url": "https://github.com/chalk/ansi-styles?sponsor=1"
             }
         },
-        "node_modules/ansi-styles/node_modules/color-convert": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-            "dev": true,
-            "dependencies": {
-                "color-name": "~1.1.4"
-            },
-            "engines": {
-                "node": ">=7.0.0"
-            }
-        },
-        "node_modules/ansi-styles/node_modules/color-name": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-            "dev": true
-        },
         "node_modules/ansicolors": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
@@ -1075,9 +955,9 @@
             }
         },
         "node_modules/axios": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
-            "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+            "version": "1.5.1",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
+            "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
             "dependencies": {
                 "follow-redirects": "^1.15.0",
                 "form-data": "^4.0.0",
@@ -1126,11 +1006,6 @@
                 "node": ">= 0.8"
             }
         },
-        "node_modules/basic-auth/node_modules/safe-buffer": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
         "node_modules/bcryptjs": {
             "version": "2.4.3",
             "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@@ -1201,6 +1076,14 @@
                 "npm": "1.2.8000 || >= 1.4.16"
             }
         },
+        "node_modules/body-parser/node_modules/bytes": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+            "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
         "node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1278,9 +1161,9 @@
             }
         },
         "node_modules/bytes": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
-            "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+            "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
             "engines": {
                 "node": ">= 0.8"
             }
@@ -1326,15 +1209,6 @@
                 "url": "https://github.com/chalk/chalk?sponsor=1"
             }
         },
-        "node_modules/chalk/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/chalk/node_modules/supports-color": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -1465,17 +1339,21 @@
             }
         },
         "node_modules/color-convert": {
-            "version": "1.9.3",
-            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+            "dev": true,
             "dependencies": {
-                "color-name": "1.1.3"
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
             }
         },
         "node_modules/color-name": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-            "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
         },
         "node_modules/color-string": {
             "version": "1.9.1",
@@ -1486,6 +1364,19 @@
                 "simple-swizzle": "^0.2.2"
             }
         },
+        "node_modules/color/node_modules/color-convert": {
+            "version": "1.9.3",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "dependencies": {
+                "color-name": "1.1.3"
+            }
+        },
+        "node_modules/color/node_modules/color-name": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+            "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+        },
         "node_modules/colorette": {
             "version": "2.0.19",
             "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
@@ -1512,11 +1403,12 @@
             }
         },
         "node_modules/commander": {
-            "version": "9.5.0",
-            "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
-            "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+            "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+            "dev": true,
             "engines": {
-                "node": "^12.20.0 || >=14"
+                "node": ">= 10"
             }
         },
         "node_modules/compressible": {
@@ -1547,19 +1439,6 @@
                 "node": ">= 0.8.0"
             }
         },
-        "node_modules/compression/node_modules/bytes": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
-            "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/compression/node_modules/safe-buffer": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
         "node_modules/concat-map": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1591,6 +1470,25 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/content-disposition/node_modules/safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ]
+        },
         "node_modules/content-type": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
@@ -1726,6 +1624,14 @@
                 "url": "https://github.com/motdotla/dotenv?sponsor=1"
             }
         },
+        "node_modules/dotenv-expand": {
+            "version": "10.0.0",
+            "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
+            "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/dotenv-vault": {
             "version": "1.25.0",
             "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.25.0.tgz",
@@ -1823,12 +1729,6 @@
                 "is-arrayish": "^0.2.1"
             }
         },
-        "node_modules/error-ex/node_modules/is-arrayish": {
-            "version": "0.2.1",
-            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-            "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-            "dev": true
-        },
         "node_modules/escalade": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1936,6 +1836,25 @@
                 "node": ">= 8.0.0"
             }
         },
+        "node_modules/express/node_modules/safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ]
+        },
         "node_modules/external-editor": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -1956,9 +1875,9 @@
             "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
         },
         "node_modules/fast-fifo": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz",
-            "integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw=="
+            "version": "1.3.2",
+            "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+            "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
         },
         "node_modules/fast-glob": {
             "version": "3.3.1",
@@ -2100,15 +2019,24 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/find-package": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz",
+            "integrity": "sha512-yVn71XCCaNgxz58ERTl8nA/8YYtIQDY9mHSrgFBfiFtdNNfY0h183Vh8BRkKxD8x9TUw3ec290uJKhDVxqGZBw==",
+            "dev": true,
+            "dependencies": {
+                "parents": "^1.0.1"
+            }
+        },
         "node_modules/fn.name": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
             "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
         },
         "node_modules/follow-redirects": {
-            "version": "1.15.2",
-            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-            "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+            "version": "1.15.3",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+            "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
             "funding": [
                 {
                     "type": "individual",
@@ -2175,9 +2103,9 @@
             }
         },
         "node_modules/fsevents": {
-            "version": "2.3.2",
-            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-            "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+            "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
             "dev": true,
             "hasInstallScript": true,
             "optional": true,
@@ -2193,6 +2121,22 @@
             "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
             "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
         },
+        "node_modules/genversion": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/genversion/-/genversion-3.1.1.tgz",
+            "integrity": "sha512-/H861PMsihhjgX2qqhTN8egM11V04imhA+3JRFY3jjPua2Sy1NqaqqQPjSP8rdM9jZoKpFhVj9g3Fs9XPCjBYQ==",
+            "dev": true,
+            "dependencies": {
+                "commander": "^7.2.0",
+                "find-package": "^1.0.0"
+            },
+            "bin": {
+                "genversion": "bin/genversion.js"
+            },
+            "engines": {
+                "node": ">=10.0.0"
+            }
+        },
         "node_modules/get-intrinsic": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@@ -2259,23 +2203,20 @@
             "dev": true
         },
         "node_modules/has": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
-            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-            "dependencies": {
-                "function-bind": "^1.1.1"
-            },
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
+            "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
             "engines": {
                 "node": ">= 0.4.0"
             }
         },
         "node_modules/has-flag": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-            "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
             "dev": true,
             "engines": {
-                "node": ">=4"
+                "node": ">=8"
             }
         },
         "node_modules/has-proto": {
@@ -2364,9 +2305,9 @@
             }
         },
         "node_modules/http-status-codes": {
-            "version": "2.2.0",
-            "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
-            "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+            "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="
         },
         "node_modules/hyperlinker": {
             "version": "1.0.0",
@@ -2494,9 +2435,10 @@
             }
         },
         "node_modules/is-arrayish": {
-            "version": "0.3.2",
-            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
-            "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+            "version": "0.2.1",
+            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+            "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+            "dev": true
         },
         "node_modules/is-binary-path": {
             "version": "2.1.0",
@@ -2511,9 +2453,9 @@
             }
         },
         "node_modules/is-core-module": {
-            "version": "2.12.1",
-            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
-            "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
+            "version": "2.13.0",
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
+            "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
             "dependencies": {
                 "has": "^1.0.3"
             },
@@ -2705,14 +2647,20 @@
             }
         },
         "node_modules/jsonwebtoken": {
-            "version": "9.0.0",
-            "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
-            "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+            "version": "9.0.2",
+            "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+            "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
             "dependencies": {
                 "jws": "^3.2.2",
-                "lodash": "^4.17.21",
+                "lodash.includes": "^4.3.0",
+                "lodash.isboolean": "^3.0.3",
+                "lodash.isinteger": "^4.0.4",
+                "lodash.isnumber": "^3.0.3",
+                "lodash.isplainobject": "^4.0.6",
+                "lodash.isstring": "^4.0.1",
+                "lodash.once": "^4.0.0",
                 "ms": "^2.1.1",
-                "semver": "^7.3.8"
+                "semver": "^7.5.4"
             },
             "engines": {
                 "node": ">=12",
@@ -2744,12 +2692,12 @@
             }
         },
         "node_modules/knex": {
-            "version": "2.4.2",
-            "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz",
-            "integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==",
+            "version": "2.5.1",
+            "resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz",
+            "integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==",
             "dependencies": {
                 "colorette": "2.0.19",
-                "commander": "^9.1.0",
+                "commander": "^10.0.0",
                 "debug": "4.3.4",
                 "escalade": "^3.1.1",
                 "esm": "^3.2.25",
@@ -2757,7 +2705,7 @@
                 "getopts": "2.3.0",
                 "interpret": "^2.2.0",
                 "lodash": "^4.17.21",
-                "pg-connection-string": "2.5.0",
+                "pg-connection-string": "2.6.1",
                 "rechoir": "^0.8.0",
                 "resolve-from": "^5.0.0",
                 "tarn": "^3.0.2",
@@ -2793,6 +2741,14 @@
                 }
             }
         },
+        "node_modules/knex/node_modules/commander": {
+            "version": "10.0.1",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+            "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+            "engines": {
+                "node": ">=14"
+            }
+        },
         "node_modules/knex/node_modules/debug": {
             "version": "4.3.4",
             "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2830,6 +2786,41 @@
             "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==",
             "dev": true
         },
+        "node_modules/lodash.includes": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+            "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+        },
+        "node_modules/lodash.isboolean": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+            "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+        },
+        "node_modules/lodash.isinteger": {
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+            "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+        },
+        "node_modules/lodash.isnumber": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+            "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+        },
+        "node_modules/lodash.isplainobject": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+            "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+        },
+        "node_modules/lodash.isstring": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+            "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+        },
+        "node_modules/lodash.once": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+            "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+        },
         "node_modules/lodash.template": {
             "version": "4.5.0",
             "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
@@ -3119,11 +3110,6 @@
                 "util-deprecate": "~1.0.1"
             }
         },
-        "node_modules/mysql/node_modules/safe-buffer": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
         "node_modules/natural-orderby": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz",
@@ -3142,9 +3128,9 @@
             }
         },
         "node_modules/node": {
-            "version": "20.5.0",
-            "resolved": "https://registry.npmjs.org/node/-/node-20.5.0.tgz",
-            "integrity": "sha512-+mVyKGaCscnINaMNIa+plgDoN9XXb5ksw7FQB78pUmodmftxomwlaU6z6ze1aCbHVB69BGqo84KkyD6NJC9Ifg==",
+            "version": "20.7.0",
+            "resolved": "https://registry.npmjs.org/node/-/node-20.7.0.tgz",
+            "integrity": "sha512-GiKqtgSALW8+W7Zi9T2AI9aME8hJg+1EESH6O7Xmk4k1gJfBKOolpy9gdg8vCyR8dMeJlp5GQZOYnvxsu8v5TA==",
             "hasInstallScript": true,
             "dependencies": {
                 "node-bin-setup": "^1.0.0"
@@ -3198,12 +3184,33 @@
                 "ms": "^2.1.1"
             }
         },
+        "node_modules/nodemon/node_modules/has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/nodemon/node_modules/ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
             "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
             "dev": true
         },
+        "node_modules/nodemon/node_modules/supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/nopt": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
@@ -6452,6 +6459,15 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/parents": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+            "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==",
+            "dev": true,
+            "dependencies": {
+                "path-platform": "~0.11.15"
+            }
+        },
         "node_modules/parse-json": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@@ -6505,6 +6521,15 @@
             "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
             "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
         },
+        "node_modules/path-platform": {
+            "version": "0.11.15",
+            "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+            "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
         "node_modules/path-to-regexp": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@@ -6520,9 +6545,9 @@
             }
         },
         "node_modules/pg-connection-string": {
-            "version": "2.5.0",
-            "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
-            "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+            "version": "2.6.1",
+            "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
+            "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg=="
         },
         "node_modules/picomatch": {
             "version": "2.3.1",
@@ -6537,13 +6562,13 @@
             }
         },
         "node_modules/prisma": {
-            "version": "5.1.1",
-            "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.1.1.tgz",
-            "integrity": "sha512-WJFG/U7sMmcc6TjJTTifTfpI6Wjoh55xl4AzopVwAdyK68L9/ogNo8QQ2cxuUjJf/Wa82z/uhyh3wMzvRIBphg==",
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.3.1.tgz",
+            "integrity": "sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==",
             "devOptional": true,
             "hasInstallScript": true,
             "dependencies": {
-                "@prisma/engines": "5.1.1"
+                "@prisma/engines": "5.3.1"
             },
             "bin": {
                 "prisma": "build/index.js"
@@ -6659,6 +6684,14 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/raw-body/node_modules/bytes": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+            "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
         "node_modules/readable-stream": {
             "version": "2.3.8",
             "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -6673,11 +6706,6 @@
                 "util-deprecate": "~1.0.1"
             }
         },
-        "node_modules/readable-stream/node_modules/safe-buffer": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
         "node_modules/readdirp": {
             "version": "3.6.0",
             "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -6719,11 +6747,11 @@
             }
         },
         "node_modules/resolve": {
-            "version": "1.22.2",
-            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
-            "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+            "version": "1.22.6",
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
+            "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==",
             "dependencies": {
-                "is-core-module": "^2.11.0",
+                "is-core-module": "^2.13.0",
                 "path-parse": "^1.0.7",
                 "supports-preserve-symlinks-flag": "^1.0.0"
             },
@@ -6807,23 +6835,9 @@
             }
         },
         "node_modules/safe-buffer": {
-            "version": "5.2.1",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ]
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
         },
         "node_modules/safe-stable-stringify": {
             "version": "2.4.3",
@@ -6947,6 +6961,11 @@
                 "is-arrayish": "^0.3.1"
             }
         },
+        "node_modules/simple-swizzle/node_modules/is-arrayish": {
+            "version": "0.3.2",
+            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+            "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+        },
         "node_modules/simple-update-notifier": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -7040,11 +7059,6 @@
                 "safe-buffer": "~5.1.0"
             }
         },
-        "node_modules/string_decoder/node_modules/safe-buffer": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
         "node_modules/string-width": {
             "version": "4.2.3",
             "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -7072,15 +7086,18 @@
             }
         },
         "node_modules/supports-color": {
-            "version": "5.5.0",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
             "dev": true,
             "dependencies": {
-                "has-flag": "^3.0.0"
+                "has-flag": "^4.0.0"
             },
             "engines": {
-                "node": ">=4"
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/supports-color?sponsor=1"
             }
         },
         "node_modules/supports-hyperlinks": {
@@ -7096,15 +7113,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/supports-hyperlinks/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/supports-hyperlinks/node_modules/supports-color": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -7252,9 +7260,12 @@
             }
         },
         "node_modules/triple-beam": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
-            "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
+            "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
+            "engines": {
+                "node": ">= 14.0.0"
+            }
         },
         "node_modules/ts-node": {
             "version": "10.9.1",
@@ -7347,9 +7358,9 @@
             "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
         },
         "node_modules/typescript": {
-            "version": "5.1.6",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
-            "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
+            "version": "5.2.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+            "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
             "dev": true,
             "bin": {
                 "tsc": "bin/tsc",
@@ -7404,9 +7415,13 @@
             }
         },
         "node_modules/uuid": {
-            "version": "9.0.0",
-            "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
-            "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+            "version": "9.0.1",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+            "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+            "funding": [
+                "https://github.com/sponsors/broofa",
+                "https://github.com/sponsors/ctavan"
+            ],
             "bin": {
                 "uuid": "dist/bin/uuid"
             }
@@ -7418,9 +7433,9 @@
             "dev": true
         },
         "node_modules/validator": {
-            "version": "13.9.0",
-            "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz",
-            "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==",
+            "version": "13.11.0",
+            "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+            "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
             "engines": {
                 "node": ">= 0.10"
             }
@@ -7470,9 +7485,9 @@
             }
         },
         "node_modules/winston": {
-            "version": "3.8.2",
-            "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz",
-            "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==",
+            "version": "3.10.0",
+            "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz",
+            "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==",
             "dependencies": {
                 "@colors/colors": "1.5.0",
                 "@dabh/diagnostics": "^2.0.2",
diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json
index e3a1c42872dff82d5049813e4d540cd5640c55e3..c251fe54ccd93983fb524e22c7c20c6ff99e0388 100644
--- a/ExpressAPI/package.json
+++ b/ExpressAPI/package.json
@@ -1,19 +1,21 @@
 {
     "name"           : "dojo_backend_api",
-    "description"    : "Backend API for the Dojo Project",
-    "version"        : "2.1.0",
-    "license"        : "",
-    "author"         : "Michaël Minelli <michael-jean.minelli@hesge.ch>",
-    "main"           : "app.js",
+    "description"    : "Backend API of the Dojo project",
+    "version"        : "2.2.0",
+    "license"        : "AGPLv3",
+    "author"         : "Michaël Minelli <dojo@minelli.me>",
+    "main"           : "dist/src/app.js",
     "scripts"        : {
         "clean"             : "rm -R dist/*",
-        "build"             : "npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
+        "dotenv:build"      : "npx dotenv-vault local build",
+        "genversion"        : "npx genversion -s -e src/config/Version.ts",
+        "build"             : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
         "database:migrate"  : "npx prisma migrate deploy",
-        "database:seed"     : "npx prisma db seed",
+        "database:seed"     : "npm run genversion; npx prisma db seed",
         "database:deploy"   : "npm run database:migrate && npm run database:seed",
-        "start:dev"         : "npx nodemon src/app.ts",
-        "start:prod"        : "NODE_ENV=production npx node --max-http-header-size=1048576 dist/src/app.js",
-        "start:migrate:prod": "npm run database:deploy && npm run start:prod"
+        "start:dev"         : "npm run genversion; npx nodemon src/app.ts",
+        "start:prod"        : "npm run genversion; NODE_ENV=production npx node --max-http-header-size=1048576 dist/src/app.js",
+        "start:migrate:prod": "npm run genversion; npm run database:deploy && npm run start:prod"
     },
     "prisma"         : {
         "seed": "node dist/prisma/seed"
@@ -26,6 +28,7 @@
         "compression"      : "^1.7.4",
         "cors"             : "^2.8.5",
         "dotenv"           : "^16.3.1",
+        "dotenv-expand"    : "^10.0.0",
         "express"          : "^4.18.2",
         "express-validator": "^7.0.1",
         "form-data"        : "^4.0.0",
@@ -39,6 +42,7 @@
         "mysql"            : "^2.18.1",
         "node"             : "^20.5.0",
         "parse-link-header": "^2.0.0",
+        "semver"           : "^7.5.4",
         "tar-stream"       : "^3.1.6",
         "uuid"             : "^9.0.0",
         "winston"          : "^3.8.2"
@@ -53,9 +57,11 @@
         "@types/multer"           : "^1.4.7",
         "@types/node"             : "^20.4.7",
         "@types/parse-link-header": "^2.0.1",
+        "@types/semver"           : "^7.5.3",
         "@types/tar-stream"       : "^2.2.2",
         "@types/uuid"             : "^9.0.2",
         "dotenv-vault"            : "^1.25.0",
+        "genversion"              : "^3.1.1",
         "nodemon"                 : "^3.0.1",
         "prisma"                  : "^5.1.1",
         "ts-node"                 : "^10.9.1",
diff --git a/ExpressAPI/src/app.ts b/ExpressAPI/src/app.ts
index 1250af3da79a7764038ae9c16bc9934e2a4a53ed..bfbc0494636c237af8dae3185fc2670eb0c51b67 100644
--- a/ExpressAPI/src/app.ts
+++ b/ExpressAPI/src/app.ts
@@ -3,10 +3,12 @@
 const path = require('node:path');
 
 if ( process.env.NODE_ENV && process.env.NODE_ENV === 'production' ) {
-    require('dotenv').config();
+    const myEnv = require('dotenv').config();
+    require('dotenv-expand').expand(myEnv);
 } else {
     require('dotenv').config({ path: path.join(__dirname, '../.env.keys') });
-    require('dotenv').config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT });
+    const myEnv = require('dotenv').config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT });
+    require('dotenv-expand').expand(myEnv);
 }
 
 require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be after the dotenv.config() calls
diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts
index 4097324791e961ef4c38172662eebc2b166e362d..574f9b2ea0e987c896a5075d4ac6dac7f7dbac55 100644
--- a/ExpressAPI/src/config/Config.ts
+++ b/ExpressAPI/src/config/Config.ts
@@ -2,6 +2,7 @@ import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
 import path             from 'path';
 import fs               from 'fs';
 import { Exercise }     from '../types/DatabaseTypes';
+import JSON5            from 'json5';
 
 
 type ConfigGitlabBadge = { link: string, imageUrl: string }
@@ -12,23 +13,25 @@ class Config {
         port: number
     };
 
-    public jwtConfig: {
+    public readonly requestClientValidation: { version: { [client: string]: string } }; // { version: { CLIENT: CONDITION } }
+
+    public readonly jwtConfig: {
         secret: string; expiresIn: number;
     };
 
-    public permissions: {
+    public readonly permissions: {
         teachingStaff: Array<string>;
     };
 
-    public gitlab: {
-        apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; }, badges: { pipeline: ConfigGitlabBadge }
+    public readonly gitlab: {
+        urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; }, badges: { pipeline: ConfigGitlabBadge }
     };
 
-    public assignment: {
+    public readonly assignment: {
         default: { description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string }; baseFiles: Array<string>; filename: string
     };
 
-    public exercise: {
+    public readonly exercise: {
         maxSameName: number; resultsFolder: string, pipelineResultsFolder: string; default: { description: string; visibility: string; };
     };
 
@@ -40,36 +43,58 @@ class Config {
             port: Number(process.env.API_PORT || 30992)
         };
 
+        this.requestClientValidation = JSON5.parse(process.env.REQUEST_CLIENT_VALIDATION || '{"version": {}}');
+
         this.jwtConfig = {
-            secret: process.env.JWT_SECRET_KEY || '', expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
+            secret   : process.env.JWT_SECRET_KEY || '',
+            expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
         };
 
         this.permissions = {
-            teachingStaff: JSON.parse(process.env.ROLES_WITH_TEACHING_STAFF_PERMISSIONS || '[]')
+            teachingStaff: JSON5.parse(process.env.ROLES_WITH_TEACHING_STAFF_PERMISSIONS || '[]')
         };
 
         this.gitlab = {
-            apiURL   : process.env.GITLAB_API_URL || '', urls: JSON.parse(process.env.GITLAB_URLS || '[]'), account: {
-                id: Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '', token: process.env.GITLAB_DOJO_ACCOUNT_TOKEN || ''
-            }, group : {
-                root: Number(process.env.GITLAB_GROUP_ROOT_ID || 0), templates: Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0), assignments: Number(process.env.GITLAB_GROUP_ASSIGNMENTS_ID || 0), exercises: Number(process.env.GITLAB_GROUP_EXERCISES_ID || 0)
-            }, badges: {
+            urls   : JSON5.parse(process.env.GITLAB_URLS || '[]'),
+            account: {
+                id      : Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0),
+                username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '',
+                token   : process.env.GITLAB_DOJO_ACCOUNT_TOKEN || ''
+            },
+            group  : {
+                root       : Number(process.env.GITLAB_GROUP_ROOT_ID || 0),
+                templates  : Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0),
+                assignments: Number(process.env.GITLAB_GROUP_ASSIGNMENTS_ID || 0),
+                exercises  : Number(process.env.GITLAB_GROUP_EXERCISES_ID || 0)
+            },
+            badges : {
                 pipeline: {
-                    link: process.env.GITLAB_BADGE_PIPELINE_LINK || '', imageUrl: process.env.GITLAB_BADGE_PIPELINE_IMAGE_URL || ''
+                    link    : process.env.GITLAB_BADGE_PIPELINE_LINK || '',
+                    imageUrl: process.env.GITLAB_BADGE_PIPELINE_IMAGE_URL || ''
                 }
             }
         };
 
         this.assignment = {
-            default     : {
-                description: process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', initReadme: process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false, sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true, visibility: process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE, wikiEnabled: process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false, template: process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? ''
-            }, baseFiles: JSON.parse(process.env.ASSIGNMENT_BASE_FILES || '[]'), filename: process.env.ASSIGNMENT_FILENAME || ''
+            default  : {
+                description         : process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
+                initReadme          : process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false,
+                sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true,
+                visibility          : process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE,
+                wikiEnabled         : process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false,
+                template            : process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? ''
+            },
+            baseFiles: JSON5.parse(process.env.ASSIGNMENT_BASE_FILES || '[]'),
+            filename : process.env.ASSIGNMENT_FILENAME || ''
         };
 
         this.exercise = {
-            maxSameName: Number(process.env.EXERCISE_MAX_SAME_NAME || 0), resultsFolder: process.env.EXERCISE_RESULTS_FOLDER?.convertWithEnvVars() ?? '', pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime
-            default    : {
-                description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', visibility: process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
+            maxSameName          : Number(process.env.EXERCISE_MAX_SAME_NAME || 0),
+            resultsFolder        : process.env.EXERCISE_RESULTS_FOLDER?.convertWithEnvVars() ?? '',
+            pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime
+            default              : {
+                description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
+                visibility : process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
             }
         };
 
diff --git a/ExpressAPI/src/controllers/Session.ts b/ExpressAPI/src/controllers/Session.ts
index fd6b85ff39d9e240795a178a1da7bdc2ab84fcc9..77549d8630df98d91b1d4b1e5ef5a7851f91621e 100644
--- a/ExpressAPI/src/controllers/Session.ts
+++ b/ExpressAPI/src/controllers/Session.ts
@@ -68,9 +68,9 @@ class Session {
      Send a response to the client
      Information: Data could be a promise or an object. If it's a promise, we wait on the data to be resolved before sending the response
      */
-    sendResponse(res: express.Response, code: number, data?: any, descriptionOverride?: string) {
+    sendResponse(res: express.Response, code: number, data?: any, descriptionOverride?: string, internalCode?: number) {
         Promise.resolve(data).then((toReturn: any) => {
-            this.getResponse(code, toReturn, descriptionOverride).then(response => {
+            this.getResponse(internalCode ?? code, toReturn, descriptionOverride).then(response => {
                 res.status(code).json(response);
             });
         });
diff --git a/ExpressAPI/src/express/API.ts b/ExpressAPI/src/express/API.ts
index d9040027335181ab03ad25d9cf4bb42cfc5ed49e..55d2fe4765ecb0810e206d456715a1da4895adae 100644
--- a/ExpressAPI/src/express/API.ts
+++ b/ExpressAPI/src/express/API.ts
@@ -1,18 +1,19 @@
-import { Express }           from 'express-serve-static-core';
-import cors                  from 'cors';
-import morganMiddleware      from '../logging/MorganMiddleware';
-import { AddressInfo }       from 'net';
-import http                  from 'http';
-import helmet                from 'helmet';
-import express               from 'express';
-import WorkerTask            from '../process/WorkerTask';
-import multer                from 'multer';
-import SessionMiddleware     from '../middlewares/SessionMiddleware';
-import Config                from '../config/Config';
-import logger                from '../shared/logging/WinstonLogger';
-import ParamsCallbackManager from '../middlewares/ParamsCallbackManager';
-import ApiRoutesManager      from '../routes/ApiRoutesManager';
-import compression           from 'compression';
+import { Express }             from 'express-serve-static-core';
+import cors                    from 'cors';
+import morganMiddleware        from '../logging/MorganMiddleware';
+import { AddressInfo }         from 'net';
+import http                    from 'http';
+import helmet                  from 'helmet';
+import express                 from 'express';
+import WorkerTask              from '../process/WorkerTask';
+import multer                  from 'multer';
+import SessionMiddleware       from '../middlewares/SessionMiddleware';
+import Config                  from '../config/Config';
+import logger                  from '../shared/logging/WinstonLogger';
+import ParamsCallbackManager   from '../middlewares/ParamsCallbackManager';
+import ApiRoutesManager        from '../routes/ApiRoutesManager';
+import compression             from 'compression';
+import ClientVersionMiddleware from '../middlewares/ClientVersionMiddleware';
 
 
 class API implements WorkerTask {
@@ -30,6 +31,8 @@ class API implements WorkerTask {
         this.backend.use(cors()); //Allow CORS requests
         this.backend.use(compression()); //Compress responses
 
+        this.backend.use(ClientVersionMiddleware.register());
+
         ParamsCallbackManager.register(this.backend);
 
         this.backend.use(SessionMiddleware.register());
diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts
index fc80949e8f8a4878cf3d891a3467c907ecd09c2a..fca3977ce07f14eada038511f9106ccf539fba0a 100644
--- a/ExpressAPI/src/managers/GitlabManager.ts
+++ b/ExpressAPI/src/managers/GitlabManager.ts
@@ -11,11 +11,12 @@ import parseLinkHeader   from 'parse-link-header';
 import GitlabFile        from '../shared/types/Gitlab/GitlabFile';
 import express           from 'express';
 import GitlabRoute       from '../shared/types/Gitlab/GitlabRoute';
+import SharedConfig      from '../shared/config/SharedConfig';
 
 
 class GitlabManager {
     private getApiUrl(route: GitlabRoute): string {
-        return `${ Config.gitlab.apiURL }${ route }`;
+        return `${ SharedConfig.gitlab.apiURL }${ route }`;
     }
 
     public async getUserById(id: number): Promise<GitlabUser | undefined> {
diff --git a/ExpressAPI/src/managers/HttpManager.ts b/ExpressAPI/src/managers/HttpManager.ts
index 80da4d4ec8abf644fa67a3bc6503f462e7625a07..6d04c68f0b0536bfa9bead9a6e78978bf5946c9b 100644
--- a/ExpressAPI/src/managers/HttpManager.ts
+++ b/ExpressAPI/src/managers/HttpManager.ts
@@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosRequestHeaders } from 'axios';
 import Config                                     from '../config/Config';
 import FormData                                   from 'form-data';
 import logger                                     from '../shared/logging/WinstonLogger';
+import SharedConfig                               from '../shared/config/SharedConfig';
 
 
 class HttpManager {
@@ -16,7 +17,7 @@ class HttpManager {
                 config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders;
             }
 
-            if ( config.url && config.url.indexOf(Config.gitlab.apiURL) !== -1 ) {
+            if ( config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 ) {
                 config.headers['PRIVATE-TOKEN'] = Config.gitlab.account.token;
             }
 
diff --git a/ExpressAPI/src/middlewares/ClientVersionMiddleware.ts b/ExpressAPI/src/middlewares/ClientVersionMiddleware.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4bed40e30357b8fb035c66e3012f582950c817d
--- /dev/null
+++ b/ExpressAPI/src/middlewares/ClientVersionMiddleware.ts
@@ -0,0 +1,35 @@
+import express            from 'express';
+import Config             from '../config/Config';
+import semver             from 'semver/preload';
+import Session            from '../controllers/Session';
+import { HttpStatusCode } from 'axios';
+import DojoStatusCode     from '../shared/types/Dojo/DojoStatusCode';
+
+
+class ClientVersionMiddleware {
+    register(): (req: express.Request, res: express.Response, next: express.NextFunction) => void {
+        return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+            if ( req.headers['client'] && req.headers['client-version'] ) {
+                const requestClient = req.headers['client'] as string;
+                const requestClientVersion = req.headers['client-version'] as string;
+
+                for ( const [ client, condition ] of Object.entries(Config.requestClientValidation.version) ) {
+                    if ( requestClient === client ) {
+                        if ( semver.satisfies(semver.valid(semver.coerce(requestClientVersion)) || '0', condition) ) {
+                            next();
+                            return;
+                        } else {
+                            new Session().sendResponse(res, HttpStatusCode.MethodNotAllowed, {}, `Client version ${ requestClientVersion } is not supported. Please update your client.`, DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED);
+                            return;
+                        }
+                    }
+                }
+
+                new Session().sendResponse(res, HttpStatusCode.MethodNotAllowed, {}, `Unsupported client.`, DojoStatusCode.CLIENT_NOT_SUPPORTED);
+            }
+        };
+    }
+}
+
+
+export default new ClientVersionMiddleware();
\ No newline at end of file
diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
index af42e42fe67513b9c926e4c9de601aaed55b2e6a..0e8926263d792b77a20c60ca36b1c64134a5f3d5 100644
--- a/ExpressAPI/src/routes/AssignmentRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -20,16 +20,26 @@ import db                             from '../helpers/DatabaseHelper';
 import { Assignment }                 from '../types/DatabaseTypes';
 import AssignmentManager              from '../managers/AssignmentManager';
 import GitlabVisibility               from '../shared/types/Gitlab/GitlabVisibility';
+import fs                             from 'fs';
+import path                           from 'path';
+import SharedAssignmentHelper         from '../shared/helpers/Dojo/SharedAssignmentHelper';
 
 
 class AssignmentRoutes implements RoutesManager {
     private readonly assignmentValidator: ExpressValidator.Schema = {
-        name       : {
-            trim: true, notEmpty: true
-        }, members : {
-            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
-        }, template: {
-            trim: true, custom: DojoValidators.templateUrlValidator, customSanitizer: DojoValidators.templateUrlSanitizer
+        name    : {
+            trim    : true,
+            notEmpty: true
+        },
+        members : {
+            trim           : true,
+            notEmpty       : true,
+            customSanitizer: DojoValidators.jsonSanitizer
+        },
+        template: {
+            trim           : true,
+            custom         : DojoValidators.templateUrlValidator,
+            customSanitizer: DojoValidators.templateUrlSanitizer
         }
     };
 
@@ -78,6 +88,8 @@ class AssignmentRoutes implements RoutesManager {
             repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template);
 
             await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
+
+            await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status');
         } catch ( error ) {
             if ( error instanceof AxiosError ) {
                 if ( error.response?.data.message.name && error.response.data.message.name == 'has already been taken' ) {
@@ -91,6 +103,18 @@ class AssignmentRoutes implements RoutesManager {
             return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
         }
 
+        try {
+            await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
+        } catch ( error ) {
+            logger.error(error);
+
+            if ( error instanceof AxiosError ) {
+                return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
+            }
+
+            return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
+        }
+
         try {
             await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => {
                 try {
@@ -102,12 +126,20 @@ class AssignmentRoutes implements RoutesManager {
 
             const assignment: Assignment = await db.assignment.create({
                                                                           data: {
-                                                                              name: repository.name, gitlabId: repository.id, gitlabLink: repository.web_url, gitlabCreationInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfoDate: new Date(), staff: {
+                                                                              name              : repository.name,
+                                                                              gitlabId          : repository.id,
+                                                                              gitlabLink        : repository.web_url,
+                                                                              gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
+                                                                              gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
+                                                                              gitlabLastInfoDate: new Date(),
+                                                                              staff             : {
                                                                                   connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                       return {
-                                                                                          create  : {
-                                                                                              gitlabId: gitlabUser.id, firstname: gitlabUser.name
-                                                                                          }, where: {
+                                                                                          create: {
+                                                                                              gitlabId : gitlabUser.id,
+                                                                                              firstname: gitlabUser.name
+                                                                                          },
+                                                                                          where : {
                                                                                               gitlabId: gitlabUser.id
                                                                                           }
                                                                                       };
@@ -129,15 +161,23 @@ class AssignmentRoutes implements RoutesManager {
 
     private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
         return async (req: express.Request, res: express.Response): Promise<void> => {
+            if ( publish ) {
+                const isPublishable = await SharedAssignmentHelper.isPublishable(req.boundParams.assignment!.gitlabId);
+                if ( !isPublishable.isPublishable ) {
+                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code);
+                }
+            }
+
             try {
                 await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);
 
                 await db.assignment.update({
-                                               where  : {
+                                               where: {
                                                    name: req.boundParams.assignment!.name
-                                               }, data: {
-                        published: publish
-                    }
+                                               },
+                                               data : {
+                                                   published: publish
+                                               }
                                            });
 
                 req.session.sendResponse(res, StatusCodes.OK);
diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts
index 0abd3d1dfcc80cf07e44d447743a431fe3b78b70..e773d8c2f08c1484a830a28edb962e4031d9de73 100644
--- a/ExpressAPI/src/routes/ExerciseRoutes.ts
+++ b/ExpressAPI/src/routes/ExerciseRoutes.ts
@@ -32,21 +32,36 @@ import ExerciseResultsFile            from '../shared/types/Dojo/ExerciseResults
 class ExerciseRoutes implements RoutesManager {
     private readonly exerciseValidator: ExpressValidator.Schema = {
         members: {
-            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
+            trim           : true,
+            notEmpty       : true,
+            customSanitizer: DojoValidators.jsonSanitizer
         }
     };
 
     private readonly resultValidator: ExpressValidator.Schema = {
-        exitCode        : {
-            isInt: true, toInt: true
-        }, commit       : {
-            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
-        }, results      : {
-            trim: true, notEmpty: true, custom: DojoValidators.exerciseResultsValidator, customSanitizer: DojoValidators.jsonSanitizer
-        }, files        : {
-            trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
-        }, archiveBase64: {
-            isBase64: true, notEmpty: true
+        exitCode     : {
+            isInt: true,
+            toInt: true
+        },
+        commit       : {
+            trim           : true,
+            notEmpty       : true,
+            customSanitizer: DojoValidators.jsonSanitizer
+        },
+        results      : {
+            trim           : true,
+            notEmpty       : true,
+            custom         : DojoValidators.exerciseResultsValidator,
+            customSanitizer: DojoValidators.jsonSanitizer
+        },
+        files        : {
+            trim           : true,
+            notEmpty       : true,
+            customSanitizer: DojoValidators.jsonSanitizer
+        },
+        archiveBase64: {
+            isBase64: true,
+            notEmpty: true
         }
     };
 
@@ -130,12 +145,23 @@ class ExerciseRoutes implements RoutesManager {
 
             const exercise: Exercise = await db.exercise.create({
                                                                     data: {
-                                                                        id: exerciseId, assignmentName: assignment.name, name: repository.name, secret: secret, gitlabId: repository.id, gitlabLink: repository.web_url, gitlabCreationInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfo: repository as unknown as Prisma.JsonObject, gitlabLastInfoDate: new Date(), members: {
+                                                                        id                : exerciseId,
+                                                                        assignmentName    : assignment.name,
+                                                                        name              : repository.name,
+                                                                        secret            : secret,
+                                                                        gitlabId          : repository.id,
+                                                                        gitlabLink        : repository.web_url,
+                                                                        gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
+                                                                        gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
+                                                                        gitlabLastInfoDate: new Date(),
+                                                                        members           : {
                                                                             connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                 return {
-                                                                                    create  : {
-                                                                                        gitlabId: gitlabUser.id, firstname: gitlabUser.name
-                                                                                    }, where: {
+                                                                                    create: {
+                                                                                        gitlabId : gitlabUser.id,
+                                                                                        firstname: gitlabUser.name
+                                                                                    },
+                                                                                    where : {
                                                                                         gitlabId: gitlabUser.id
                                                                                     }
                                                                                 };
@@ -186,7 +212,9 @@ class ExerciseRoutes implements RoutesManager {
         }));
 
         return req.session.sendResponse(res, StatusCodes.OK, {
-            assignment: (req.boundParams.exercise as Exercise).assignment, assignmentFile: dojoAssignmentFile, immutable: immutableFiles
+            assignment    : (req.boundParams.exercise as Exercise).assignment,
+            assignmentFile: dojoAssignmentFile,
+            immutable     : immutableFiles
         });
     }
 
@@ -196,7 +224,12 @@ class ExerciseRoutes implements RoutesManager {
 
         const result = await db.result.create({
                                                   data: {
-                                                      exerciseId: exercise.id, exitCode: params.exitCode, success: params.results.success, commit: params.commit, results: params.results as unknown as Prisma.JsonObject, files: params.files
+                                                      exerciseId: exercise.id,
+                                                      exitCode  : params.exitCode,
+                                                      success   : params.results.success!,
+                                                      commit    : params.commit,
+                                                      results   : params.results as unknown as Prisma.JsonObject,
+                                                      files     : params.files
                                                   }
                                               });
 
diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared
index 6cff19278f7fa9664793e1d7826d2596597ac080..efe1bf313f57d1826faf935c183d37a0835f8c2d 160000
--- a/ExpressAPI/src/shared
+++ b/ExpressAPI/src/shared
@@ -1 +1 @@
-Subproject commit 6cff19278f7fa9664793e1d7826d2596597ac080
+Subproject commit efe1bf313f57d1826faf935c183d37a0835f8c2d