From a1ab6547e463b3291fba1d1265c579ed5834bcd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me>
Date: Thu, 23 Nov 2023 20:38:47 +0100
Subject: [PATCH] OpenAPI => First try (aborted): Use a library for generate
 openapi file from code and comment

---
 .gitignore                                |    3 +
 ExpressAPI/nodemon.json                   |    2 +-
 ExpressAPI/package-lock.json              | 1220 ++++++++++++++++++++-
 ExpressAPI/package.json                   |   60 +-
 ExpressAPI/src/express/API.ts             |   65 +-
 ExpressAPI/src/openapi.ts                 |  134 +++
 ExpressAPI/src/routes/AssignmentRoutes.ts |   20 +
 ExpressAPI/src/routes/BaseRoutes.ts       |   19 +
 ExpressAPI/src/routes/ExerciseRoutes.ts   |   38 +
 ExpressAPI/src/routes/GitlabRoutes.ts     |    5 +
 ExpressAPI/src/routes/SessionRoutes.ts    |  144 +++
 11 files changed, 1656 insertions(+), 54 deletions(-)
 create mode 100644 ExpressAPI/src/openapi.ts

diff --git a/.gitignore b/.gitignore
index 58df814..e74d858 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@ workspace.xml
 Wiki/.idea
 
 ExpressAPI/src/config/Version.ts
+openapi.json
+redoc.html
+
 
 ############################ MacOS
 # General
diff --git a/ExpressAPI/nodemon.json b/ExpressAPI/nodemon.json
index cbfbc0e..220b7ed 100644
--- a/ExpressAPI/nodemon.json
+++ b/ExpressAPI/nodemon.json
@@ -9,5 +9,5 @@
     "verbose": true,
     "ext"    : ".ts,.js",
     "ignore" : [],
-    "exec"   : "npm run lint; ts-node --files ./src/app.ts"
+    "exec"   : "npm run lint; npm run build:openapi; ts-node --files ./src/app.ts"
 }
diff --git a/ExpressAPI/package-lock.json b/ExpressAPI/package-lock.json
index 5af8cec..549a842 100644
--- a/ExpressAPI/package-lock.json
+++ b/ExpressAPI/package-lock.json
@@ -30,11 +30,13 @@
                 "node": "^20.5.0",
                 "parse-link-header": "^2.0.0",
                 "semver": "^7.5.4",
+                "swagger-ui-express": "^5.0.0",
                 "tar-stream": "^3.1.6",
                 "uuid": "^9.0.0",
                 "winston": "^3.8.2"
             },
             "devDependencies": {
+                "@redocly/cli": "^1.4.1",
                 "@types/compression": "^1.7.2",
                 "@types/cors": "^2.8.13",
                 "@types/express": "^4.17.17",
@@ -44,6 +46,7 @@
                 "@types/node": "^20.4.7",
                 "@types/parse-link-header": "^2.0.1",
                 "@types/semver": "^7.5.3",
+                "@types/swagger-ui-express": "^4.1.6",
                 "@types/tar-stream": "^2.2.2",
                 "@types/uuid": "^9.0.2",
                 "@typescript-eslint/eslint-plugin": "^6.10.0",
@@ -53,6 +56,7 @@
                 "nodemon": "^3.0.1",
                 "npm": "^9.8.1",
                 "prisma": "^5.1.1",
+                "swagger-autogen": "^2.23.7",
                 "ts-node": "^10.9.1",
                 "typescript": "^5.1.6"
             }
@@ -67,6 +71,18 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/@babel/runtime": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
+            "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==",
+            "dev": true,
+            "dependencies": {
+                "regenerator-runtime": "^0.14.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
         "node_modules/@colors/colors": {
             "version": "1.6.0",
             "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
@@ -97,6 +113,27 @@
                 "kuler": "^2.0.0"
             }
         },
+        "node_modules/@emotion/is-prop-valid": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+            "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+            "dev": true,
+            "dependencies": {
+                "@emotion/memoize": "^0.8.1"
+            }
+        },
+        "node_modules/@emotion/memoize": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+            "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+            "dev": true
+        },
+        "node_modules/@emotion/unitless": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+            "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+            "dev": true
+        },
         "node_modules/@eslint-community/eslint-utils": {
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -224,6 +261,12 @@
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
             }
         },
+        "node_modules/@exodus/schemasafe": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
+            "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==",
+            "dev": true
+        },
         "node_modules/@humanwhocodes/config-array": {
             "version": "0.11.13",
             "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -768,6 +811,133 @@
             "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz",
             "integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA=="
         },
+        "node_modules/@redocly/ajv": {
+            "version": "8.11.0",
+            "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
+            "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==",
+            "dev": true,
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "json-schema-traverse": "^1.0.0",
+                "require-from-string": "^2.0.2",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
+        "node_modules/@redocly/cli": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-1.4.1.tgz",
+            "integrity": "sha512-c0v8SYyqC1QvImJrGhw6+wIPXn10Zhp1sUGmvXIIXZQzS7fL15+qBH4f19nh3ChpSdO4x0EdBFvvTovrupV4sQ==",
+            "dev": true,
+            "dependencies": {
+                "@redocly/openapi-core": "1.4.1",
+                "chokidar": "^3.5.1",
+                "colorette": "^1.2.0",
+                "core-js": "^3.32.1",
+                "get-port-please": "^3.0.1",
+                "glob": "^7.1.6",
+                "handlebars": "^4.7.6",
+                "mobx": "^6.0.4",
+                "node-fetch": "^2.6.1",
+                "react": "^17.0.0 || ^18.2.0",
+                "react-dom": "^17.0.0 || ^18.2.0",
+                "redoc": "~2.1.2",
+                "semver": "^7.5.2",
+                "simple-websocket": "^9.0.0",
+                "styled-components": "^6.0.7",
+                "yargs": "17.0.1"
+            },
+            "bin": {
+                "openapi": "bin/cli.js",
+                "redocly": "bin/cli.js"
+            },
+            "engines": {
+                "node": ">=14.19.0",
+                "npm": ">=7.0.0"
+            }
+        },
+        "node_modules/@redocly/cli/node_modules/colorette": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+            "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+            "dev": true
+        },
+        "node_modules/@redocly/openapi-core": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.4.1.tgz",
+            "integrity": "sha512-oAhnG8MKocM9LuP++NGFxdniNKWSLA7hzHPQoOK92LIP/DdvXx8pEeZ68UTNxIXhKonoUcO6s86I3L0zj143zg==",
+            "dev": true,
+            "dependencies": {
+                "@redocly/ajv": "^8.11.0",
+                "@types/node": "^14.11.8",
+                "colorette": "^1.2.0",
+                "js-levenshtein": "^1.1.6",
+                "js-yaml": "^4.1.0",
+                "lodash.isequal": "^4.5.0",
+                "minimatch": "^5.0.1",
+                "node-fetch": "^2.6.1",
+                "pluralize": "^8.0.0",
+                "yaml-ast-parser": "0.0.43"
+            },
+            "engines": {
+                "node": ">=14.19.0",
+                "npm": ">=7.0.0"
+            }
+        },
+        "node_modules/@redocly/openapi-core/node_modules/@types/node": {
+            "version": "14.18.63",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+            "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+            "dev": true
+        },
+        "node_modules/@redocly/openapi-core/node_modules/argparse": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+            "dev": true
+        },
+        "node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+            "dev": true,
+            "dependencies": {
+                "balanced-match": "^1.0.0"
+            }
+        },
+        "node_modules/@redocly/openapi-core/node_modules/colorette": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+            "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+            "dev": true
+        },
+        "node_modules/@redocly/openapi-core/node_modules/js-yaml": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+            "dev": true,
+            "dependencies": {
+                "argparse": "^2.0.1"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/@redocly/openapi-core/node_modules/minimatch": {
+            "version": "5.1.6",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+            "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+            "dev": true,
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/@tsconfig/node10": {
             "version": "1.0.9",
             "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@@ -961,6 +1131,22 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/stylis": {
+            "version": "4.2.4",
+            "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.4.tgz",
+            "integrity": "sha512-36ZrGJ8fgtBr6nwNnuJ9jXIj+bn/pF6UoqmrQT7+Y99+tFFeHHsoR54+194dHdyhPjgbeoNz3Qru0oRt0l6ASQ==",
+            "dev": true
+        },
+        "node_modules/@types/swagger-ui-express": {
+            "version": "4.1.6",
+            "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz",
+            "integrity": "sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg==",
+            "dev": true,
+            "dependencies": {
+                "@types/express": "*",
+                "@types/serve-static": "*"
+            }
+        },
         "node_modules/@types/tar-stream": {
             "version": "2.2.3",
             "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.3.tgz",
@@ -1674,6 +1860,12 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/call-me-maybe": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
+            "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
+            "dev": true
+        },
         "node_modules/callsites": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1684,6 +1876,15 @@
                 "node": ">=6"
             }
         },
+        "node_modules/camelize": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+            "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/cardinal": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz",
@@ -1764,6 +1965,12 @@
             "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
             "dev": true
         },
+        "node_modules/classnames": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+            "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==",
+            "dev": true
+        },
         "node_modules/clean-stack": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz",
@@ -1824,6 +2031,17 @@
                 "node": ">= 10"
             }
         },
+        "node_modules/cliui": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+            "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+            "dev": true,
+            "dependencies": {
+                "string-width": "^4.2.0",
+                "strip-ansi": "^6.0.0",
+                "wrap-ansi": "^7.0.0"
+            }
+        },
         "node_modules/clone": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@@ -1833,6 +2051,15 @@
                 "node": ">=0.8"
             }
         },
+        "node_modules/clsx": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+            "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/color": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@@ -2014,6 +2241,17 @@
             "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
             "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
         },
+        "node_modules/core-js": {
+            "version": "3.33.3",
+            "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
+            "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
+            "dev": true,
+            "hasInstallScript": true,
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/core-js"
+            }
+        },
         "node_modules/core-util-is": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -2051,6 +2289,32 @@
                 "node": ">= 8"
             }
         },
+        "node_modules/css-color-keywords": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+            "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/css-to-react-native": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+            "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+            "dev": true,
+            "dependencies": {
+                "camelize": "^1.0.0",
+                "css-color-keywords": "^1.0.0",
+                "postcss-value-parser": "^4.0.2"
+            }
+        },
+        "node_modules/csstype": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+            "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+            "dev": true
+        },
         "node_modules/debug": {
             "version": "2.6.9",
             "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2059,6 +2323,12 @@
                 "ms": "2.0.0"
             }
         },
+        "node_modules/decko": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz",
+            "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==",
+            "dev": true
+        },
         "node_modules/deep-is": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2066,6 +2336,15 @@
             "dev": true,
             "peer": true
         },
+        "node_modules/deepmerge": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+            "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/defaults": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -2137,6 +2416,12 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/dompurify": {
+            "version": "2.4.7",
+            "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
+            "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
+            "dev": true
+        },
         "node_modules/dotenv": {
             "version": "16.3.1",
             "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
@@ -2253,6 +2538,12 @@
                 "is-arrayish": "^0.2.1"
             }
         },
+        "node_modules/es6-promise": {
+            "version": "3.3.1",
+            "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+            "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
+            "dev": true
+        },
         "node_modules/escalade": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -2538,6 +2829,12 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/eventemitter3": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+            "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+            "dev": true
+        },
         "node_modules/express": {
             "version": "4.18.2",
             "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -2666,6 +2963,12 @@
                 "fastest-levenshtein": "^1.0.7"
             }
         },
+        "node_modules/fast-safe-stringify": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+            "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+            "dev": true
+        },
         "node_modules/fastest-levenshtein": {
             "version": "1.0.16",
             "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -2866,6 +3169,12 @@
                 }
             }
         },
+        "node_modules/foreach": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
+            "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==",
+            "dev": true
+        },
         "node_modules/form-data": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -2920,8 +3229,7 @@
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
             "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-            "dev": true,
-            "peer": true
+            "dev": true
         },
         "node_modules/fsevents": {
             "version": "2.3.3",
@@ -2961,6 +3269,15 @@
                 "node": ">=10.0.0"
             }
         },
+        "node_modules/get-caller-file": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+            "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+            "dev": true,
+            "engines": {
+                "node": "6.* || 8.* || >= 10.*"
+            }
+        },
         "node_modules/get-intrinsic": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@@ -2983,6 +3300,12 @@
                 "node": ">=8.0.0"
             }
         },
+        "node_modules/get-port-please": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.1.tgz",
+            "integrity": "sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA==",
+            "dev": true
+        },
         "node_modules/getopts": {
             "version": "2.3.0",
             "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
@@ -2993,7 +3316,6 @@
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
             "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "fs.realpath": "^1.0.0",
                 "inflight": "^1.0.4",
@@ -3082,6 +3404,27 @@
             "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
             "dev": true
         },
+        "node_modules/handlebars": {
+            "version": "4.7.8",
+            "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+            "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+            "dev": true,
+            "dependencies": {
+                "minimist": "^1.2.5",
+                "neo-async": "^2.6.2",
+                "source-map": "^0.6.1",
+                "wordwrap": "^1.0.0"
+            },
+            "bin": {
+                "handlebars": "bin/handlebars"
+            },
+            "engines": {
+                "node": ">=0.4.7"
+            },
+            "optionalDependencies": {
+                "uglify-js": "^3.1.4"
+            }
+        },
         "node_modules/has": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
@@ -3189,6 +3532,12 @@
             "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
             "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="
         },
+        "node_modules/http2-client": {
+            "version": "1.3.5",
+            "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz",
+            "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==",
+            "dev": true
+        },
         "node_modules/hyperlinker": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz",
@@ -3295,7 +3644,6 @@
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
             "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
             "dev": true,
-            "peer": true,
             "dependencies": {
                 "once": "^1.3.0",
                 "wrappy": "1"
@@ -3537,6 +3885,21 @@
                 "node": ">=10"
             }
         },
+        "node_modules/js-levenshtein": {
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+            "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "dev": true
+        },
         "node_modules/js-yaml": {
             "version": "3.14.1",
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
@@ -3563,6 +3926,15 @@
             "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
             "dev": true
         },
+        "node_modules/json-pointer": {
+            "version": "0.6.2",
+            "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
+            "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
+            "dev": true,
+            "dependencies": {
+                "foreach": "^2.0.4"
+            }
+        },
         "node_modules/json-schema-traverse": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -3788,6 +4160,12 @@
             "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
             "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
         },
+        "node_modules/lodash.isequal": {
+            "version": "4.5.0",
+            "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+            "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+            "dev": true
+        },
         "node_modules/lodash.isinteger": {
             "version": "4.0.4",
             "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@@ -3894,6 +4272,18 @@
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
             "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
         },
+        "node_modules/loose-envify": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+            "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+            "dev": true,
+            "dependencies": {
+                "js-tokens": "^3.0.0 || ^4.0.0"
+            },
+            "bin": {
+                "loose-envify": "cli.js"
+            }
+        },
         "node_modules/lru-cache": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -3905,12 +4295,36 @@
                 "node": ">=10"
             }
         },
+        "node_modules/lunr": {
+            "version": "2.3.9",
+            "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+            "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
+            "dev": true
+        },
         "node_modules/make-error": {
             "version": "1.3.6",
             "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
+        "node_modules/mark.js": {
+            "version": "8.11.1",
+            "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
+            "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==",
+            "dev": true
+        },
+        "node_modules/marked": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+            "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+            "dev": true,
+            "bin": {
+                "marked": "bin/marked.js"
+            },
+            "engines": {
+                "node": ">= 12"
+            }
+        },
         "node_modules/media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -4030,6 +4444,63 @@
             "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
             "dev": true
         },
+        "node_modules/mobx": {
+            "version": "6.11.0",
+            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.11.0.tgz",
+            "integrity": "sha512-qngYCmr0WJiFRSAtYe82DB7SbzvbhehkJjONs8ydynUwoazzUQHZdAlaJqUfks5j4HarhWsZrMRhV7HtSO9HOQ==",
+            "dev": true,
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mobx"
+            }
+        },
+        "node_modules/mobx-react": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.6.0.tgz",
+            "integrity": "sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA==",
+            "dev": true,
+            "dependencies": {
+                "mobx-react-lite": "^3.4.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mobx"
+            },
+            "peerDependencies": {
+                "mobx": "^6.1.0",
+                "react": "^16.8.0 || ^17 || ^18"
+            },
+            "peerDependenciesMeta": {
+                "react-dom": {
+                    "optional": true
+                },
+                "react-native": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/mobx-react-lite": {
+            "version": "3.4.3",
+            "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz",
+            "integrity": "sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg==",
+            "dev": true,
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mobx"
+            },
+            "peerDependencies": {
+                "mobx": "^6.1.0",
+                "react": "^16.8.0 || ^17 || ^18"
+            },
+            "peerDependenciesMeta": {
+                "react-dom": {
+                    "optional": true
+                },
+                "react-native": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/morgan": {
             "version": "1.10.0",
             "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -4112,7 +4583,25 @@
                 "util-deprecate": "~1.0.1"
             }
         },
-        "node_modules/natural-compare": {
+        "node_modules/nanoid": {
+            "version": "3.3.7",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+            "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "bin": {
+                "nanoid": "bin/nanoid.cjs"
+            },
+            "engines": {
+                "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+            }
+        },
+        "node_modules/natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
             "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
@@ -4135,6 +4624,12 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/neo-async": {
+            "version": "2.6.2",
+            "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+            "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+            "dev": true
+        },
         "node_modules/node": {
             "version": "20.8.1",
             "resolved": "https://registry.npmjs.org/node/-/node-20.8.1.tgz",
@@ -4155,6 +4650,47 @@
             "resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.1.3.tgz",
             "integrity": "sha512-opgw9iSCAzT2+6wJOETCpeRYAQxSopqQ2z+N6BXwIMsQQ7Zj5M8MaafQY8JMlolRR6R1UXg2WmhKp0p9lSOivg=="
         },
+        "node_modules/node-fetch": {
+            "version": "2.7.0",
+            "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+            "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+            "dev": true,
+            "dependencies": {
+                "whatwg-url": "^5.0.0"
+            },
+            "engines": {
+                "node": "4.x || >=6.0.0"
+            },
+            "peerDependencies": {
+                "encoding": "^0.1.0"
+            },
+            "peerDependenciesMeta": {
+                "encoding": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/node-fetch-h2": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz",
+            "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==",
+            "dev": true,
+            "dependencies": {
+                "http2-client": "^1.2.5"
+            },
+            "engines": {
+                "node": "4.x || >=6.0.0"
+            }
+        },
+        "node_modules/node-readfiles": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz",
+            "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==",
+            "dev": true,
+            "dependencies": {
+                "es6-promise": "^3.2.1"
+            }
+        },
         "node_modules/nodemon": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
@@ -7391,6 +7927,76 @@
             "inBundle": true,
             "license": "ISC"
         },
+        "node_modules/oas-kit-common": {
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz",
+            "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==",
+            "dev": true,
+            "dependencies": {
+                "fast-safe-stringify": "^2.0.7"
+            }
+        },
+        "node_modules/oas-linter": {
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz",
+            "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==",
+            "dev": true,
+            "dependencies": {
+                "@exodus/schemasafe": "^1.0.0-rc.2",
+                "should": "^13.2.1",
+                "yaml": "^1.10.0"
+            },
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
+        "node_modules/oas-resolver": {
+            "version": "2.5.6",
+            "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz",
+            "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==",
+            "dev": true,
+            "dependencies": {
+                "node-fetch-h2": "^2.3.0",
+                "oas-kit-common": "^1.0.8",
+                "reftools": "^1.1.9",
+                "yaml": "^1.10.0",
+                "yargs": "^17.0.1"
+            },
+            "bin": {
+                "resolve": "resolve.js"
+            },
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
+        "node_modules/oas-schema-walker": {
+            "version": "1.1.5",
+            "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz",
+            "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
+        "node_modules/oas-validator": {
+            "version": "5.0.8",
+            "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz",
+            "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==",
+            "dev": true,
+            "dependencies": {
+                "call-me-maybe": "^1.0.1",
+                "oas-kit-common": "^1.0.8",
+                "oas-linter": "^3.2.2",
+                "oas-resolver": "^2.5.6",
+                "oas-schema-walker": "^1.1.5",
+                "reftools": "^1.1.9",
+                "should": "^13.2.1",
+                "yaml": "^1.10.0"
+            },
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
         "node_modules/object-assign": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7467,6 +8073,16 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/openapi-sampler": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.1.tgz",
+            "integrity": "sha512-Ert9mvc2tLPmmInwSyGZS+v4Ogu9/YoZuq9oP3EdUklg2cad6+IGndP9yqJJwbgdXwZibiq5fpv6vYujchdJFg==",
+            "dev": true,
+            "dependencies": {
+                "@types/json-schema": "^7.0.7",
+                "json-pointer": "0.6.2"
+            }
+        },
         "node_modules/optionator": {
             "version": "0.9.3",
             "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -7617,6 +8233,12 @@
                 "cross-spawn": "^7.0.3"
             }
         },
+        "node_modules/path-browserify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+            "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+            "dev": true
+        },
         "node_modules/path-exists": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -7632,7 +8254,6 @@
             "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
             "dev": true,
-            "peer": true,
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -7674,11 +8295,23 @@
                 "node": ">=8"
             }
         },
+        "node_modules/perfect-scrollbar": {
+            "version": "1.5.5",
+            "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz",
+            "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==",
+            "dev": true
+        },
         "node_modules/pg-connection-string": {
             "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/picocolors": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+            "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+            "dev": true
+        },
         "node_modules/picomatch": {
             "version": "2.3.1",
             "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -7691,6 +8324,61 @@
                 "url": "https://github.com/sponsors/jonschlinkert"
             }
         },
+        "node_modules/pluralize": {
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+            "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/polished": {
+            "version": "4.2.2",
+            "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz",
+            "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==",
+            "dev": true,
+            "dependencies": {
+                "@babel/runtime": "^7.17.8"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/postcss": {
+            "version": "8.4.31",
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+            "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "opencollective",
+                    "url": "https://opencollective.com/postcss/"
+                },
+                {
+                    "type": "tidelift",
+                    "url": "https://tidelift.com/funding/github/npm/postcss"
+                },
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "dependencies": {
+                "nanoid": "^3.3.6",
+                "picocolors": "^1.0.0",
+                "source-map-js": "^1.0.2"
+            },
+            "engines": {
+                "node": "^10 || ^12 || >=14"
+            }
+        },
+        "node_modules/postcss-value-parser": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+            "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+            "dev": true
+        },
         "node_modules/prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -7717,11 +8405,31 @@
                 "node": ">=16.13"
             }
         },
+        "node_modules/prismjs": {
+            "version": "1.29.0",
+            "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+            "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/process-nextick-args": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
             "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
         },
+        "node_modules/prop-types": {
+            "version": "15.8.1",
+            "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+            "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+            "dev": true,
+            "dependencies": {
+                "loose-envify": "^1.4.0",
+                "object-assign": "^4.1.1",
+                "react-is": "^16.13.1"
+            }
+        },
         "node_modules/proxy-addr": {
             "version": "2.0.7",
             "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -7802,6 +8510,15 @@
             "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
             "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
         },
+        "node_modules/randombytes": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+            "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+            "dev": true,
+            "dependencies": {
+                "safe-buffer": "^5.1.0"
+            }
+        },
         "node_modules/range-parser": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -7832,6 +8549,50 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/react": {
+            "version": "18.2.0",
+            "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+            "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+            "dev": true,
+            "dependencies": {
+                "loose-envify": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/react-dom": {
+            "version": "18.2.0",
+            "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+            "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+            "dev": true,
+            "dependencies": {
+                "loose-envify": "^1.1.0",
+                "scheduler": "^0.23.0"
+            },
+            "peerDependencies": {
+                "react": "^18.2.0"
+            }
+        },
+        "node_modules/react-is": {
+            "version": "16.13.1",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+            "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+            "dev": true
+        },
+        "node_modules/react-tabs": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz",
+            "integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==",
+            "dev": true,
+            "dependencies": {
+                "clsx": "^1.1.0",
+                "prop-types": "^15.5.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-0 || ^18.0.0"
+            }
+        },
         "node_modules/readable-stream": {
             "version": "2.3.8",
             "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -7878,6 +8639,70 @@
                 "esprima": "~4.0.0"
             }
         },
+        "node_modules/redoc": {
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.1.3.tgz",
+            "integrity": "sha512-d7F9qLLxaiFW4GC03VkwlX9wuRIpx9aiIIf3o6mzMnqPfhxrn2IRKGndrkJeVdItgCfmg9jXZiFEowm60f1meQ==",
+            "dev": true,
+            "dependencies": {
+                "@redocly/openapi-core": "^1.0.0-rc.2",
+                "classnames": "^2.3.1",
+                "decko": "^1.2.0",
+                "dompurify": "^2.2.8",
+                "eventemitter3": "^4.0.7",
+                "json-pointer": "^0.6.2",
+                "lunr": "^2.3.9",
+                "mark.js": "^8.11.1",
+                "marked": "^4.0.15",
+                "mobx-react": "^7.2.0",
+                "openapi-sampler": "^1.3.1",
+                "path-browserify": "^1.0.1",
+                "perfect-scrollbar": "^1.5.5",
+                "polished": "^4.1.3",
+                "prismjs": "^1.27.0",
+                "prop-types": "^15.7.2",
+                "react-tabs": "^4.3.0",
+                "slugify": "~1.4.7",
+                "stickyfill": "^1.1.1",
+                "swagger2openapi": "^7.0.6",
+                "url-template": "^2.0.8"
+            },
+            "engines": {
+                "node": ">=6.9",
+                "npm": ">=3.0.0"
+            },
+            "peerDependencies": {
+                "core-js": "^3.1.4",
+                "mobx": "^6.0.4",
+                "react": "^16.8.4 || ^17.0.0 || ^18.0.0",
+                "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0",
+                "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5"
+            }
+        },
+        "node_modules/reftools": {
+            "version": "1.1.9",
+            "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz",
+            "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
+        "node_modules/regenerator-runtime": {
+            "version": "0.14.0",
+            "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
+            "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
+            "dev": true
+        },
+        "node_modules/require-directory": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+            "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/require-from-string": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -8008,6 +8833,15 @@
             "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
             "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
         },
+        "node_modules/scheduler": {
+            "version": "0.23.0",
+            "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+            "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+            "dev": true,
+            "dependencies": {
+                "loose-envify": "^1.1.0"
+            }
+        },
         "node_modules/semver": {
             "version": "7.5.4",
             "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@@ -8069,6 +8903,12 @@
             "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
             "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
         },
+        "node_modules/shallowequal": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+            "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+            "dev": true
+        },
         "node_modules/shebang-command": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8090,6 +8930,60 @@
                 "node": ">=8"
             }
         },
+        "node_modules/should": {
+            "version": "13.2.3",
+            "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
+            "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
+            "dev": true,
+            "dependencies": {
+                "should-equal": "^2.0.0",
+                "should-format": "^3.0.3",
+                "should-type": "^1.4.0",
+                "should-type-adaptors": "^1.0.1",
+                "should-util": "^1.0.0"
+            }
+        },
+        "node_modules/should-equal": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+            "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+            "dev": true,
+            "dependencies": {
+                "should-type": "^1.4.0"
+            }
+        },
+        "node_modules/should-format": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+            "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==",
+            "dev": true,
+            "dependencies": {
+                "should-type": "^1.3.0",
+                "should-type-adaptors": "^1.0.1"
+            }
+        },
+        "node_modules/should-type": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+            "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==",
+            "dev": true
+        },
+        "node_modules/should-type-adaptors": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+            "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+            "dev": true,
+            "dependencies": {
+                "should-type": "^1.3.0",
+                "should-util": "^1.0.0"
+            }
+        },
+        "node_modules/should-util": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
+            "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
+            "dev": true
+        },
         "node_modules/side-channel": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -8134,6 +9028,70 @@
                 "node": ">=10"
             }
         },
+        "node_modules/simple-websocket": {
+            "version": "9.1.0",
+            "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz",
+            "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ],
+            "dependencies": {
+                "debug": "^4.3.1",
+                "queue-microtask": "^1.2.2",
+                "randombytes": "^2.1.0",
+                "readable-stream": "^3.6.0",
+                "ws": "^7.4.2"
+            }
+        },
+        "node_modules/simple-websocket/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dev": true,
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/simple-websocket/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/simple-websocket/node_modules/readable-stream": {
+            "version": "3.6.2",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+            "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+            "dev": true,
+            "dependencies": {
+                "inherits": "^2.0.3",
+                "string_decoder": "^1.1.1",
+                "util-deprecate": "^1.0.1"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
         "node_modules/slash": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -8160,6 +9118,33 @@
                 "url": "https://github.com/chalk/slice-ansi?sponsor=1"
             }
         },
+        "node_modules/slugify": {
+            "version": "1.4.7",
+            "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz",
+            "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.0.0"
+            }
+        },
+        "node_modules/source-map": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/source-map-js": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+            "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/sprintf-js": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -8190,6 +9175,12 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/stickyfill": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz",
+            "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==",
+            "dev": true
+        },
         "node_modules/streamsearch": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -8254,6 +9245,40 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/styled-components": {
+            "version": "6.1.1",
+            "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz",
+            "integrity": "sha512-cpZZP5RrKRIClBW5Eby4JM1wElLVP4NQrJbJ0h10TidTyJf4SIIwa3zLXOoPb4gJi8MsJ8mjq5mu2IrEhZIAcQ==",
+            "dev": true,
+            "dependencies": {
+                "@emotion/is-prop-valid": "^1.2.1",
+                "@emotion/unitless": "^0.8.0",
+                "@types/stylis": "^4.0.2",
+                "css-to-react-native": "^3.2.0",
+                "csstype": "^3.1.2",
+                "postcss": "^8.4.31",
+                "shallowequal": "^1.1.0",
+                "stylis": "^4.3.0",
+                "tslib": "^2.5.0"
+            },
+            "engines": {
+                "node": ">= 16"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/styled-components"
+            },
+            "peerDependencies": {
+                "react": ">= 16.8.0",
+                "react-dom": ">= 16.8.0"
+            }
+        },
+        "node_modules/stylis": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz",
+            "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==",
+            "dev": true
+        },
         "node_modules/supports-color": {
             "version": "8.1.1",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -8305,6 +9330,76 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/swagger-autogen": {
+            "version": "2.23.7",
+            "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz",
+            "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==",
+            "dev": true,
+            "dependencies": {
+                "acorn": "^7.4.1",
+                "deepmerge": "^4.2.2",
+                "glob": "^7.1.7",
+                "json5": "^2.2.3"
+            }
+        },
+        "node_modules/swagger-autogen/node_modules/acorn": {
+            "version": "7.4.1",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+            "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+            "dev": true,
+            "bin": {
+                "acorn": "bin/acorn"
+            },
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/swagger-ui-dist": {
+            "version": "5.10.0",
+            "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.10.0.tgz",
+            "integrity": "sha512-PBTn5qDOQVtU29hrx74km86SnK3/mFtF3grI98y575y1aRpxiuStRTIvsfXFudPFkLofHU7H9a+fKrP+Oayc3g=="
+        },
+        "node_modules/swagger-ui-express": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.0.tgz",
+            "integrity": "sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==",
+            "dependencies": {
+                "swagger-ui-dist": ">=5.0.0"
+            },
+            "engines": {
+                "node": ">= v0.10.32"
+            },
+            "peerDependencies": {
+                "express": ">=4.0.0 || >=5.0.0-beta"
+            }
+        },
+        "node_modules/swagger2openapi": {
+            "version": "7.0.8",
+            "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz",
+            "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==",
+            "dev": true,
+            "dependencies": {
+                "call-me-maybe": "^1.0.1",
+                "node-fetch": "^2.6.1",
+                "node-fetch-h2": "^2.3.0",
+                "node-readfiles": "^0.2.0",
+                "oas-kit-common": "^1.0.8",
+                "oas-resolver": "^2.5.6",
+                "oas-schema-walker": "^1.1.5",
+                "oas-validator": "^5.0.8",
+                "reftools": "^1.1.9",
+                "yaml": "^1.10.0",
+                "yargs": "^17.0.1"
+            },
+            "bin": {
+                "boast": "boast.js",
+                "oas-validate": "oas-validate.js",
+                "swagger2openapi": "swagger2openapi.js"
+            },
+            "funding": {
+                "url": "https://github.com/Mermade/oas-kit?sponsor=1"
+            }
+        },
         "node_modules/tar-fs": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
@@ -8435,6 +9530,12 @@
                 "nodetouch": "bin/nodetouch.js"
             }
         },
+        "node_modules/tr46": {
+            "version": "0.0.3",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+            "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+            "dev": true
+        },
         "node_modules/triple-beam": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
@@ -8571,6 +9672,19 @@
                 "node": ">=14.17"
             }
         },
+        "node_modules/uglify-js": {
+            "version": "3.17.4",
+            "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
+            "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
+            "dev": true,
+            "optional": true,
+            "bin": {
+                "uglifyjs": "bin/uglifyjs"
+            },
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
         "node_modules/undefsafe": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -8608,6 +9722,12 @@
                 "punycode": "^2.1.0"
             }
         },
+        "node_modules/url-template": {
+            "version": "2.0.8",
+            "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
+            "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
+            "dev": true
+        },
         "node_modules/util-deprecate": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -8664,6 +9784,22 @@
                 "defaults": "^1.0.3"
             }
         },
+        "node_modules/webidl-conversions": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+            "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+            "dev": true
+        },
+        "node_modules/whatwg-url": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+            "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+            "dev": true,
+            "dependencies": {
+                "tr46": "~0.0.3",
+                "webidl-conversions": "^3.0.0"
+            }
+        },
         "node_modules/which": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -8780,6 +9916,27 @@
             "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
             "dev": true
         },
+        "node_modules/ws": {
+            "version": "7.5.9",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+            "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.3.0"
+            },
+            "peerDependencies": {
+                "bufferutil": "^4.0.1",
+                "utf-8-validate": "^5.0.2"
+            },
+            "peerDependenciesMeta": {
+                "bufferutil": {
+                    "optional": true
+                },
+                "utf-8-validate": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/xtend": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -8788,11 +9945,62 @@
                 "node": ">=0.4"
             }
         },
+        "node_modules/y18n": {
+            "version": "5.0.8",
+            "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+            "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/yallist": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "node_modules/yaml": {
+            "version": "1.10.2",
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+            "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+            "dev": true,
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/yaml-ast-parser": {
+            "version": "0.0.43",
+            "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+            "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+            "dev": true
+        },
+        "node_modules/yargs": {
+            "version": "17.0.1",
+            "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz",
+            "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==",
+            "dev": true,
+            "dependencies": {
+                "cliui": "^7.0.2",
+                "escalade": "^3.1.1",
+                "get-caller-file": "^2.0.5",
+                "require-directory": "^2.1.1",
+                "string-width": "^4.2.0",
+                "y18n": "^5.0.5",
+                "yargs-parser": "^20.2.2"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/yargs-parser": {
+            "version": "20.2.9",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+            "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/yn": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json
index 30b657f..73ab186 100644
--- a/ExpressAPI/package.json
+++ b/ExpressAPI/package.json
@@ -10,7 +10,9 @@
         "dotenv:build"      : "npx dotenv-vault local build",
         "lint"              : "npx eslint .",
         "genversion"        : "npx genversion -s -e src/config/Version.ts",
-        "build"             : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
+        "build:openapi"     : "npx ts-node src/openapi.ts; npx @redocly/cli build-docs assets/openapi.json --output=assets/redoc.html",
+        "build:project"     : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
+        "build"             : "npm run build:openapi; npm run build:project",
         "database:migrate"  : "npx prisma migrate deploy",
         "database:seed"     : "npm run genversion; npx prisma db seed",
         "database:deploy"   : "npm run database:migrate && npm run database:seed",
@@ -22,32 +24,34 @@
         "seed": "node dist/prisma/seed"
     },
     "dependencies"   : {
-        "@prisma/client"   : "^5.1.1",
-        "ajv"              : "^8.12.0",
-        "axios"            : "^1.4.0",
-        "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",
-        "helmet"           : "^7.0.0",
-        "http-status-codes": "^2.2.0",
-        "json5"            : "^2.2.3",
-        "jsonwebtoken"     : "^9.0.0",
-        "knex"             : "^2.4.2",
-        "morgan"           : "^1.10.0",
-        "multer"           : "^1.4.5-lts.1",
-        "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"
+        "@prisma/client"    : "^5.1.1",
+        "ajv"               : "^8.12.0",
+        "axios"             : "^1.4.0",
+        "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",
+        "helmet"            : "^7.0.0",
+        "http-status-codes" : "^2.2.0",
+        "json5"             : "^2.2.3",
+        "jsonwebtoken"      : "^9.0.0",
+        "knex"              : "^2.4.2",
+        "morgan"            : "^1.10.0",
+        "multer"            : "^1.4.5-lts.1",
+        "mysql"             : "^2.18.1",
+        "node"              : "^20.5.0",
+        "parse-link-header" : "^2.0.0",
+        "semver"            : "^7.5.4",
+        "swagger-ui-express": "^5.0.0",
+        "tar-stream"        : "^3.1.6",
+        "uuid"              : "^9.0.0",
+        "winston"           : "^3.8.2"
     },
     "devDependencies": {
+        "@redocly/cli"                    : "^1.4.1",
         "@types/compression"              : "^1.7.2",
         "@types/cors"                     : "^2.8.13",
         "@types/express"                  : "^4.17.17",
@@ -57,6 +61,7 @@
         "@types/node"                     : "^20.4.7",
         "@types/parse-link-header"        : "^2.0.1",
         "@types/semver"                   : "^7.5.3",
+        "@types/swagger-ui-express"       : "^4.1.6",
         "@types/tar-stream"               : "^2.2.2",
         "@types/uuid"                     : "^9.0.2",
         "@typescript-eslint/eslint-plugin": "^6.10.0",
@@ -64,9 +69,10 @@
         "dotenv-vault"                    : "^1.25.0",
         "genversion"                      : "^3.1.1",
         "nodemon"                         : "^3.0.1",
+        "npm"                             : "^9.8.1",
         "prisma"                          : "^5.1.1",
+        "swagger-autogen"                 : "^2.23.7",
         "ts-node"                         : "^10.9.1",
-        "typescript"                      : "^5.1.6",
-        "npm"                             : "^9.8.1"
+        "typescript"                      : "^5.1.6"
     }
 }
diff --git a/ExpressAPI/src/express/API.ts b/ExpressAPI/src/express/API.ts
index de0be93..0c42647 100644
--- a/ExpressAPI/src/express/API.ts
+++ b/ExpressAPI/src/express/API.ts
@@ -1,19 +1,21 @@
-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 ClientVersionCheckerMiddleware from '../middlewares/ClientVersionCheckerMiddleware';
+import swaggerUi                      from 'swagger-ui-express';
+import path                           from 'path';
 
 
 class API implements WorkerTask {
@@ -23,6 +25,7 @@ class API implements WorkerTask {
     constructor() {
         this.backend = express();
 
+        this.initSwagger();
         this.initBaseMiddlewares();
 
         this.backend.use(ClientVersionCheckerMiddleware.register());
@@ -41,13 +44,35 @@ 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);
+    private initSwagger() {
+        const options = {
+            swaggerOptions: {
+                url: '/docs/openapi.json'
+            }
+        };
+        this.backend.get('/docs/openapi.json', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/openapi.json')));
+        this.backend.use('/docs/swagger', swaggerUi.serveFiles(undefined, options), swaggerUi.setup(undefined, options));
+        this.backend.get('/docs/redoc.html', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/redoc.html')));
 
-        this.backend.use(SessionMiddleware.register());
-
-        ApiRoutesManager.registerOnBackend(this.backend);
+        this.backend.get('/docs/', (req, res) => {
+            res.send(`
+                <!DOCTYPE html>
+                <html lang="en">
+                <body>
+                    <ul>
+                        <li><a href="/docs/openapi.json">OpenAPI</a></li>
+                        <li>GUI
+                            <ul>
+                                <li><a href="/docs/swagger">Swagger</a></li>
+                                <li><a href="/docs/redoc.html">Redoc</a></li>
+                            </ul>
+                        </li>
+                    </ul>
+                </body>
+                </html>
+            `);
+        });
     }
 
     run() {
diff --git a/ExpressAPI/src/openapi.ts b/ExpressAPI/src/openapi.ts
new file mode 100644
index 0000000..eeadd8c
--- /dev/null
+++ b/ExpressAPI/src/openapi.ts
@@ -0,0 +1,134 @@
+require('./InitialImports'); // ATTENTION : These lines MUST be the first of this file
+
+import swaggerAutogen from 'swagger-autogen';
+import { version }    from './config/Version';
+import Config         from './config/Config';
+
+
+const doc = {
+    info      : {
+        title      : 'Dojo API',
+        version    : version,
+        description: '**Backend API of the Dojo project.**\n\nSee more information about the projet on [Gitlab](https://githepia.hesge.ch/dojo_project/dojo).',
+        license    : {
+            name: 'AGPLv3',
+            url : 'https://githepia.hesge.ch/dojo_project/projects/backend/dojobackendapi/-/blob/main/LICENSE'
+        },
+        contact    : {
+            name : 'Michaƫl Minelli',
+            email: 'dojo@minelli.me'
+        }
+    },
+    servers   : [ {
+        url        : `http://localhost:${ Config.api.port }/`,
+        description: 'Development'
+    }, {
+        url        : `http://dojo-test.edu.hesge.ch/dojo/api/`,
+        description: 'Test (only from HES-GE network)'
+    }, {
+        url        : `https://rdps.hesge.ch/dojo/api/`,
+        description: 'Production'
+    } ],
+    tags      : [ {
+        name       : 'General',
+        description: ''
+    }, {
+        name       : 'Session',
+        description: 'Routes that are used to manage the user\'s session'
+    }, {
+        name       : 'Gitlab',
+        description: 'Routes that are used to provide Gitlab informations'
+    }, {
+        name       : 'Assignment',
+        description: 'Routes that are used to manage assignments'
+    }, {
+        name       : 'Exercise',
+        description: 'Routes that are used to manage exercises'
+    } ],
+    consumes  : [ 'multipart/form-data' ],
+    produces  : [ 'application/json' ],
+    components: {
+        securitySchemes: {
+            'Clients token'         : {
+                type        : 'http',
+                scheme      : 'bearer',
+                bearerFormat: 'JWT'
+            },
+            'ExerciseChecker secret': {
+                type: 'apiKey',
+                in  : 'header',
+                name: 'ExerciseSecret'
+            }
+        },
+        schemas        : {
+            DojoBackendResponse: {
+                $timestamp   : '717876000000',
+                $code        : 200,
+                $description : 'OK',
+                $sessionToken: 'JWT token (for content, see schema named \'SessionTokenJWT\')',
+                $data        : {}
+            },
+            User               : {
+                $id             : 142,
+                name            : 'michael.minelli',
+                mail            : 'dojo@minelli.me',
+                $role           : {
+                    '@enum': [ 'STUDENT', 'TEACHING_STAFF', 'ADMIN' ]
+                },
+                $gitlabUsername : 'michael.minelli',
+                gitlabLastInfo  : {},
+                $isTeachingStaff: true,
+                $isAdmin        : true,
+                $deleted        : false,
+                assignments     : [ {
+                    $ref: '#/components/schemas/Assignment'
+                } ],
+                exercises       : [ {
+                    $ref: '#/components/schemas/Exercise'
+                } ]
+            },
+            Assignment         : {
+                $name              : 'C_Hello_World',
+                $gitlabId          : 30992,
+                $gitlabLink        : 'https://githepia.hesge.ch/dojo_project',
+                $gitlabCreationInfo: {},
+                $gitlabLastInfo    : {},
+                $gitlabLastInfoDate: '1992-09-30 19:00:00.000',
+                $published         : true,
+                $staff             : [ {
+                    $ref: '#/components/schemas/User'
+                } ],
+                $exercises         : [ {
+                    $ref: '#/components/schemas/Exercise'
+                } ]
+            },
+            Exercise           : {
+                $id                : 'eb5f2182-f5b1-42a9-80fc-cad384571053',
+                $assignmentName    : 'C_Hello_World',
+                $name              : 'DojoEx - C_Hello_World - michael.minelli',
+                $gitlabId          : 93092,
+                $gitlabLink        : 'https://githepia.hesge.ch/dojo_project/dojo',
+                $gitlabCreationInfo: {},
+                $gitlabLastInfo    : {},
+                $gitlabLastInfoDate: '1992-09-30 19:00:00.000'
+            },
+            SessionTokenJWT    : {
+                $profile: {
+                    $ref: '#/components/schemas/User'
+                },
+                $iat    : '1700749215'
+            }
+        }
+    }
+};
+
+const options = {
+    openapi   : '3.1.0',
+    autoHeader: false,
+    autoBody  : false
+};
+
+const outputFile = '../assets/openapi.json';
+const routes = [ './routes/*.ts' ];
+
+swaggerAutogen(options)(outputFile, routes, doc);
\ No newline at end of file
diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
index 605bbc4..2ee10e8 100644
--- a/ExpressAPI/src/routes/AssignmentRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -55,6 +55,11 @@ class AssignmentRoutes implements RoutesManager {
 
     // Get an assignment by its name or gitlab url
     private async getAssignment(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Assignment']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         const assignment: Assignment | undefined = req.boundParams.assignment;
 
         if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment, req.session.profile) ) {
@@ -85,6 +90,11 @@ class AssignmentRoutes implements RoutesManager {
     }
 
     private async createAssignment(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Assignment']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         const params: {
             name: string, members: Array<GitlabUser>, template: string
         } = req.body;
@@ -168,10 +178,20 @@ class AssignmentRoutes implements RoutesManager {
     }
 
     private async publishAssignment(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Assignment']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         return this.changeAssignmentPublishedStatus(true)(req, res);
     }
 
     private async unpublishAssignment(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Assignment']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         return this.changeAssignmentPublishedStatus(false)(req, res);
     }
 
diff --git a/ExpressAPI/src/routes/BaseRoutes.ts b/ExpressAPI/src/routes/BaseRoutes.ts
index c0ac80a..e8355ce 100644
--- a/ExpressAPI/src/routes/BaseRoutes.ts
+++ b/ExpressAPI/src/routes/BaseRoutes.ts
@@ -11,10 +11,29 @@ class BaseRoutes implements RoutesManager {
     }
 
     private async homepage(req: express.Request, res: express.Response) {
+        /*
+         #swagger.ignore = true
+         */
+
         return req.session.sendResponse(res, StatusCodes.OK);
     }
 
     private async healthCheck(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['General']
+         #swagger.summary = 'Health check'
+         #swagger.description = 'This route can be used to check if the server is up and running.'
+         #swagger.responses[200] = {
+            content: {
+                "application/json": {
+                    schema:{
+                        $ref: "#/components/schemas/DojoBackendResponse"
+                    }
+                }
+            }
+        }
+         */
+
         return req.session.sendResponse(res, StatusCodes.OK);
     }
 }
diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts
index 1b9bc1d..4afec93 100644
--- a/ExpressAPI/src/routes/ExerciseRoutes.ts
+++ b/ExpressAPI/src/routes/ExerciseRoutes.ts
@@ -85,6 +85,11 @@ class ExerciseRoutes implements RoutesManager {
     }
 
     private async createExercise(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Exercise']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         const params: { members: Array<GitlabUser> } = req.body;
         params.members = [ await req.session.profile.gitlabProfile!.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id);
         const assignment: Assignment = req.boundParams.assignment!;
@@ -185,6 +190,34 @@ class ExerciseRoutes implements RoutesManager {
     }
 
     private async getAssignment(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Exercise', 'Assignment']
+         #swagger.description = 'Endpoint to get the specific user.'
+         #swagger.responses[200] = {
+             description: "Some description...",
+             content: {
+                 "application/json": {
+                     schema:{
+                        allOf: [
+                            { $ref: "#/components/schemas/DojoBackendResponse" },
+                            {
+                                type       : 'object',
+                                properties : {
+                                    data: {
+                                        type: 'object',
+                                        properties: {
+                                            assignment: { $ref: "#/components/schemas/Assignment" }
+                                        }
+                                    }
+                                }
+                             }
+                        ]
+                     }
+                 }
+             }
+         }
+         */
+
         const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
 
         let assignmentHjsonFile!: GitlabFile;
@@ -221,6 +254,11 @@ class ExerciseRoutes implements RoutesManager {
     }
 
     private async createResult(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Exercise']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         const params: { exitCode: number, commit: Record<string, string>, results: ExerciseResultsFile, files: Array<IFileDirStat>, archiveBase64: string } = req.body;
         const exercise: Exercise = req.boundParams.exercise!;
 
diff --git a/ExpressAPI/src/routes/GitlabRoutes.ts b/ExpressAPI/src/routes/GitlabRoutes.ts
index 40ad2d9..cd06688 100644
--- a/ExpressAPI/src/routes/GitlabRoutes.ts
+++ b/ExpressAPI/src/routes/GitlabRoutes.ts
@@ -12,6 +12,11 @@ class GitlabRoutes implements RoutesManager {
     }
 
     private async checkTemplateAccess(req: express.Request, res: express.Response) {
+        /*
+         #swagger.tags = ['Gitlab']
+         #swagger.description = 'Endpoint to get the specific user.'
+         */
+
         const idOrNamespace: string = req.params.idOrNamespace;
 
         return res.status(await GitlabManager.checkTemplateAccess(idOrNamespace, req)).send();
diff --git a/ExpressAPI/src/routes/SessionRoutes.ts b/ExpressAPI/src/routes/SessionRoutes.ts
index a5bd42f..4eb832f 100644
--- a/ExpressAPI/src/routes/SessionRoutes.ts
+++ b/ExpressAPI/src/routes/SessionRoutes.ts
@@ -38,6 +38,51 @@ class SessionRoutes implements RoutesManager {
     }
 
     private async login(req: express.Request, res: express.Response) {
+        /*
+        #swagger.tags = ['Session']
+        #swagger.summary = 'Login to Dojo app'
+        #swagger.description = 'This route can be used to connect the user to the backend and retrieve informations about his access rights.'
+        #swagger.requestBody = {
+            content: {
+                "multipart/form-data": {
+                    schema: {
+                        type: 'object',
+                        properties: {
+                            accessToken: {
+                                type: 'string',
+                                format: 'Gitlab access token'
+                            },
+                            refreshToken: {
+                                type: 'string',
+                                format: 'Gitlab refresh token'
+                            }
+                        },
+                        required: ['accessToken', 'refreshToken']
+                    }
+                }
+            }
+        }
+        #swagger.responses[200] = {
+            content: {
+                "application/json": {
+                    schema:{
+                        $ref: "#/components/schemas/DojoBackendResponse"
+                    }
+                }
+            }
+        }
+        #swagger.responses[404] = {
+            description: "Can't retrieve user informations from Gitlab with the provided access token.",
+            content: {
+                "application/json": {
+                    schema:{
+                        $ref: "#/components/schemas/DojoBackendResponse"
+                    }
+                }
+            }
+        }
+         */
+
         try {
             const params: {
                 accessToken: string, refreshToken: string
@@ -59,6 +104,90 @@ class SessionRoutes implements RoutesManager {
     }
 
     private async refreshTokens(req: express.Request, res: express.Response) {
+        /*
+        #swagger.tags = ['Session']
+        #swagger.summary = 'Refresh tokens'
+        #swagger.description = 'This route can be used to refresh the session. Gitlab tokens will be refreshed and a new Dojo backend JWT token will be provided.'
+        #swagger.requestBody = {
+            content: {
+                "multipart/form-data": {
+                    schema: {
+                        type: 'object',
+                        properties: {
+                            refreshToken: {
+                                type: 'string',
+                                format: 'Gitlab refresh token'
+                            }
+                        },
+                        required: ['refreshToken']
+                    }
+                }
+            }
+        }
+        #swagger.responses[200] = {
+            description: 'The new Gitlab tokens as returned by Gitlab API.',
+            content: {
+                "application/json": {
+                    schema:{
+                        allOf: [
+                            { $ref: "#/components/schemas/DojoBackendResponse" },
+                            {
+                                type       : 'object',
+                                properties : {
+                                    data: {
+                                        type: 'object',
+                                        properties: {
+                                            "access_token": {
+                                                "type": "string",
+                                                "example": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54"
+                                            },
+                                            "token_type": {
+                                                "type": "string",
+                                                "example": "bearer"
+                                            },
+                                            "expires_in": {
+                                                "type": "number",
+                                                "example": 7200
+                                            },
+                                            "refresh_token": {
+                                                "type": "string",
+                                                "example": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
+                                            },
+                                            "scope": {
+                                                "type": "array",
+                                                "example": [
+                                                    "api",
+                                                    "create_runner",
+                                                    "read_repository",
+                                                    "write_repository"
+                                                ],
+                                                "items": {
+                                                    "type": "string"
+                                                }
+                                            },
+                                            "created_at": {
+                                                "type": "number",
+                                                "example": 1607635748
+                                            }
+                                        },
+                                        "required": [
+                                            "access_token",
+                                            "token_type",
+                                            "expires_in",
+                                            "refresh_token",
+                                            "scope",
+                                            "created_at"
+                                        ]
+                                    }
+                                }
+                            }
+                        ]
+                     }
+                 }
+             }
+        }
+         */
+
         try {
             const params: {
                 refreshToken: string
@@ -73,6 +202,21 @@ class SessionRoutes implements RoutesManager {
     }
 
     private async testSession(req: express.Request, res: express.Response) {
+        /*
+        #swagger.tags = ['Session']
+        #swagger.summary = 'Test of the session'
+        #swagger.description = 'This route can be used to test the validity of the session token.'
+        #swagger.responses[200] = {
+            content: {
+                "application/json": {
+                    schema:{
+                        $ref: "#/components/schemas/DojoBackendResponse"
+                    }
+                }
+            }
+        }
+         */
+
         req.session.sendResponse(res, StatusCodes.OK);
     }
 }
-- 
GitLab