semi-stable-state - non-working

Co-authored-by: MathieuSevignyLavallee <MathieuSevignyLavallee@users.noreply.github.com>
This commit is contained in:
Gabriel Matte 2024-11-10 15:42:46 -05:00
parent bbc0359ead
commit c26708a609
22 changed files with 2209 additions and 1671 deletions

25
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug backend",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/server/app.js",
"cwd":"${workspaceFolder}/server/"
},
{
"type": "msedge",
"request": "launch",
"name": "Debug frontend",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/client/"
}
]
}

309
client/package-lock.json generated
View file

@ -19,6 +19,7 @@
"@mui/material": "^6.1.0",
"@types/uuid": "^9.0.7",
"axios": "^1.6.7",
"dockerode": "^4.0.2",
"esbuild": "^0.23.1",
"gift-pegjs": "^1.0.2",
"jest-environment-jsdom": "^29.7.0",
@ -1897,6 +1898,12 @@
"node": ">=6.9.0"
}
},
"node_modules/@balena/dockerignore": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
"integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==",
"license": "Apache-2.0"
},
"node_modules/@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@ -5190,6 +5197,15 @@
"dequal": "^2.0.3"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@ -5458,6 +5474,35 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -5469,6 +5514,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -5542,12 +5598,45 @@
"node-int64": "^0.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/buildcheck": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -5658,6 +5747,12 @@
"node": ">= 6"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
@ -5786,6 +5881,20 @@
"node": ">=10"
}
},
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "~0.0.6",
"nan": "^2.19.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/create-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
@ -6057,6 +6166,35 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/docker-modem": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz",
"integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.1.1",
"readable-stream": "^3.5.0",
"split-ca": "^1.0.1",
"ssh2": "^1.15.0"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/dockerode": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz",
"integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==",
"license": "Apache-2.0",
"dependencies": {
"@balena/dockerignore": "^1.0.2",
"docker-modem": "^5.0.3",
"tar-fs": "~2.0.1"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@ -6123,6 +6261,15 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
@ -6797,6 +6944,12 @@
"node": ">= 6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
@ -7078,6 +7231,26 @@
"node": ">=4"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -7153,8 +7326,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-arrayish": {
"version": "0.2.1",
@ -10208,11 +10380,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/nan": {
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
"integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
"license": "MIT",
"optional": true
},
"node_modules/nanoid": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
@ -10284,7 +10469,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"dependencies": {
"wrappy": "1"
}
@ -10661,6 +10845,16 @@
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"node_modules/pump": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -10805,6 +10999,20 @@
"react-dom": ">=16.6.0"
}
},
"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==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -11047,6 +11255,26 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -11182,12 +11410,35 @@
"node": ">=0.10.0"
}
},
"node_modules/split-ca": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
"integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==",
"license": "ISC"
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/ssh2": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.20.0"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@ -11207,6 +11458,15 @@
"node": ">=8"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@ -11319,6 +11579,34 @@
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
},
"node_modules/tar-fs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@ -11539,6 +11827,12 @@
}
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -11765,6 +12059,12 @@
"requires-port": "^1.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@ -12637,8 +12937,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/write-file-atomic": {
"version": "4.0.2",

View file

@ -23,6 +23,7 @@
"@mui/material": "^6.1.0",
"@types/uuid": "^9.0.7",
"axios": "^1.6.7",
"dockerode": "^4.0.2",
"esbuild": "^0.23.1",
"gift-pegjs": "^1.0.2",
"jest-environment-jsdom": "^29.7.0",

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { Socket } from 'socket.io-client';
import { ENV_VARIABLES } from '../../../constants';
//import { ENV_VARIABLES } from '../../../constants';
import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz';
import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz';
@ -27,14 +27,14 @@ const JoinRoom: React.FC = () => {
const [isConnecting, setIsConnecting] = useState<boolean>(false);
useEffect(() => {
handleCreateSocket();
//handleCreateSocket();
return () => {
disconnect();
};
}, []);
const handleCreateSocket = () => {
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
const socket = webSocketService.connect("localhost:4500");
socket.on('join-success', () => {
setIsWaitingForTeacher(true);

View file

@ -10,7 +10,7 @@ import webSocketService, { AnswerReceptionFromBackendType } from '../../../servi
import { QuizType } from '../../../Types/QuizType';
import './manageRoom.css';
import { ENV_VARIABLES } from '../../../constants';
//import { ENV_VARIABLES } from '../../../constants';
import { StudentType, Answer } from '../../../Types/StudentType';
import { Button } from '@mui/material';
import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle';
@ -81,7 +81,7 @@ const ManageRoom: React.FC = () => {
const createWebSocketRoom = () => {
setConnectingError('');
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
const socket = webSocketService.connect("localhost:4500");
socket.on('connect', () => {
webSocketService.createRoom();

85
docker-compose.local.yaml Normal file
View file

@ -0,0 +1,85 @@
version: '3'
services:
frontend:
build:
context: ./client
dockerfile: Dockerfile
container_name: frontend
ports:
- "5173:5173"
restart: always
backend:
build:
context: ./server
dockerfile: Dockerfile
container_name: backend
ports:
- "3000:3000"
environment:
PORT: 3000
MONGO_URI: "mongodb://mongo:27017/evaluetonsavoir"
MONGO_DATABASE: evaluetonsavoir
EMAIL_SERVICE: gmail
SENDER_EMAIL: infoevaluetonsavoir@gmail.com
EMAIL_PSW: 'vvml wmfr dkzb vjzb'
JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe
FRONTEND_URL: "http://localhost:5173"
depends_on:
- mongo
restart: always
quizroom:
build:
context: ./quizRoom
dockerfile: Dockerfile
container_name: quizroom
ports:
- "4500:4500"
depends_on:
- backend
restart: always
# Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
container_name: nginx
ports:
- "80:80"
depends_on:
- backend
- frontend
restart: always
# Ce conteneur est la base de données principale pour l'application
mongo:
image: mongo
container_name: mongo
ports:
- "27017:27017"
tty: true
volumes:
- mongodb_data:/data/db
restart: always
# Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=America/Montreal
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_DEBUG=true
- WATCHTOWER_INCLUDE_RESTARTING=true
- WATCHTOWER_SCHEDULE=0 0 5 * * * # At 5 am everyday
restart: always
volumes:
mongodb_data:
external: false

View file

@ -60,13 +60,6 @@ services:
- mongodb_data:/data/db
restart: always
# Ce conteneur contient la base de donnée cache nécessaire au bon fonctionnement des salles de quiz
valkey:
image: valkey/valkey:alpine
container_name: valkey
volumes:
- ./deployments/valkey.conf:/usr/local/etc/valkey/valkey.conf
# Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure
watchtower:
image: containrrr/watchtower

View file

@ -1,6 +1,5 @@
import http from "http";
import { Server, ServerOptions } from "socket.io";
import { GlideClient, GlideClientConfiguration } from '@valkey/valkey-glide';
// Import setupWebsocket
import { setupWebsocket } from "./socket/setupWebSocket";

View file

@ -18,6 +18,13 @@ const users = require('./models/users.js');
const userModel = new users(db, foldersModel);
const images = require('./models/images.js');
const imageModel = new images(db);
const {RoomRepository} = require('./models/room.js');
const roomRepModel = new RoomRepository(db);
// Instantiate the controllers
const QuizProviderOptions = {
provider: 'docker'
};
// instantiate the controllers
const usersController = require('./controllers/users.js');
@ -28,18 +35,22 @@ const quizController = require('./controllers/quiz.js');
const quizControllerInstance = new quizController(quizModel, foldersModel);
const imagesController = require('./controllers/images.js');
const imagesControllerInstance = new imagesController(imageModel);
const roomsController = require('./controllers/rooms.js');
const roomsControllerInstance = new roomsController(QuizProviderOptions,roomRepModel);
// export the controllers
module.exports.users = usersControllerInstance;
module.exports.folders = foldersControllerInstance;
module.exports.quizzes = quizControllerInstance;
module.exports.images = imagesControllerInstance;
module.exports.rooms = roomsControllerInstance;
//import routers (instantiate controllers as side effect)
const userRouter = require('./routers/users.js');
const folderRouter = require('./routers/folders.js');
const quizRouter = require('./routers/quiz.js');
const imagesRouter = require('./routers/images.js');
const roomRouter = require('./routers/rooms.js');
// Setup environment
dotenv.config();
@ -50,6 +61,7 @@ const app = express();
const cors = require("cors");
const bodyParser = require('body-parser');
const configureServer = (httpServer, isDev) => {
return new Server(httpServer, {
path: "/socket.io",
@ -80,6 +92,7 @@ app.use('/api/user', userRouter);
app.use('/api/folder', folderRouter);
app.use('/api/quiz', quizRouter);
app.use('/api/image', imagesRouter);
app.use('/api/room', roomRouter);
app.use(errorHandler);

View file

@ -0,0 +1,64 @@
const BaseRoomProvider = require('../roomsProviders/base-provider.js');
//const ClusterRoomProvider = require('../roomsProviders/cluster-provider.js');
const DockerRoomProvider = require('../roomsProviders/docker-provider.js');
//const KubernetesRoomProvider = require('../roomsProviders/kubernetes-provider');
class RoomsController {
constructor(options = {}, roomRepository) {
this.provider = this.createProvider(
options.provider || process.env.ROOM_PROVIDER || 'cluster',
options.providerOptions,
roomRepository
);
this.roomRepository = roomRepository;
this.setupCleanup();
}
createProvider(type, options, repository) {
switch (type) {
/*
case 'cluster':
return new ClusterRoomProvider(options, this.roomRepository);
*/
// Uncomment these as needed
case 'docker':
return new DockerRoomProvider(options, repository);
/*
case 'kubernetes':
return new KubernetesRoomProvider(options);
*/
default:
throw new Error(`Unknown provider type: ${type}`);
}
}
setupCleanup() {
setInterval(() => {
this.provider.cleanup().catch(console.error);
}, 30000);
}
async createRoom(options = {}) {
const roomId = options.roomId || this.generateRoomId();
return await this.provider.createRoom(roomId, options);
}
async deleteRoom(roomId) {
return await this.provider.deleteRoom(roomId);
}
async getRoomStatus(roomId) {
return await this.provider.getRoomStatus(roomId);
}
async listRooms() {
return await this.provider.listRooms();
}
generateRoomId() {
return Math.random().toString(36).substring(2, 7);
}
}
module.exports = RoomsController;

View file

@ -1,75 +0,0 @@
import { GlideClient, GlideClientConfiguration } from '@valkey/valkey-glide';
import {
RoomInfo,
RoomOptions,
ProviderType,
ProviderConfig
} from '../../types/room';
import { BaseRoomProvider } from './providers/base-provider';
import { ClusterRoomProvider } from './providers/cluster-provider';
import { DockerRoomProvider } from './providers/docker-provider';
import { KubernetesRoomProvider } from './providers/kubernetes-provider';
interface RoomManagerOptions {
valkeyConfig?: GlideClientConfiguration;
provider?: ProviderType;
providerOptions?: ProviderConfig;
}
export class RoomManager {
private valkey: GlideClient;
private provider: BaseRoomProvider<RoomInfo>;
constructor(options: RoomManagerOptions = {}) {
this.valkey = new GlideClient();
this.provider = this.createProvider(
options.provider || process.env.ROOM_PROVIDER as ProviderType || 'cluster',
options.providerOptions
);
this.setupCleanup();
}
private createProvider(
type: ProviderType,
options?: ProviderConfig
): BaseRoomProvider<RoomInfo> {
switch (type) {
case 'cluster':
return new ClusterRoomProvider(this.redis, options);
case 'docker':
return new DockerRoomProvider(this.redis, options);
case 'kubernetes':
return new KubernetesRoomProvider(this.redis, options);
default:
throw new Error(`Unknown provider type: ${type}`);
}
}
private setupCleanup(): void {
setInterval(() => {
this.provider.cleanup().catch(console.error);
}, 30000);
}
async createRoom(options: RoomOptions = {}): Promise<RoomInfo> {
const roomId = options.roomId || this.generateRoomId();
return await this.provider.createRoom(roomId, options);
}
async deleteRoom(roomId: string): Promise<void> {
return await this.provider.deleteRoom(roomId);
}
async getRoomStatus(roomId: string): Promise<RoomInfo | null> {
return await this.provider.getRoomStatus(roomId);
}
async listRooms(): Promise<RoomInfo[]> {
return await this.provider.listRooms();
}
private generateRoomId(): string {
return `room-${Math.random().toString(36).substr(2, 9)}`;
}
}

View file

@ -51,6 +51,7 @@ class Quiz {
await this.db.connect()
const conn = this.db.getConnection();
const quizCollection = conn.collection('files');
const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) });

68
server/models/room.js Normal file
View file

@ -0,0 +1,68 @@
class Room {
constructor(id,name,host,nbStudents){
this.id = id;
this.name = name;
this.host = host;
this.nbStudents = nbStudents;
}
}
class RoomRepository{
constructor(db) {
this.db = db;
this.connection = null;
this.collection = null;
}
async init(){
if(!this.connection){
await this.db.connect()
this.connection = this.db.getConnection();
}
if(!this.collection) this.collection = this.connection.collection('rooms');
}
async create(room){
await this.init();
const existingRoom = await this.collection.findOne({ id: room.id });
if (existingRoom) {
throw new Error(`Room already exists with id: ${room.id}`);
}
const result = await this.collection.insertOne(room);
return result.insertedId;
}
async get(id) {
await this.init();
const existingRoom = await this.collection.findOne({ id: id });
return existingRoom
}
async getAll() {
await this.init();
const result = await this.collection.find().toArray();
return result;
}
async update(room){
await this.init();
const result = await this.collection.updateOne(
{ id: room.id },
{
$set: room
}
);
return result.modifiedCount === 1;
}
async delete(id){
await this.init();
const result = await this.collection.deleteMany({ id: id });
return result.deletedCount > 0;
}
}
module.exports = {Room, RoomRepository};

2770
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,9 +14,9 @@
"license": "MIT",
"dependencies": {
"@types/express": "^5.0.0",
"@valkey/valkey-glide": "^1.1.0",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dockerode": "^4.0.2",
"dotenv": "^16.4.4",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",

View file

@ -0,0 +1,55 @@
/**
* @template T
* @typedef {import('../../types/room').RoomInfo} RoomInfo
* @typedef {import('../../types/room').RoomOptions} RoomOptions
* @typedef {import('../../types/room').BaseProviderConfig} BaseProviderConfig
*/
class BaseRoomProvider {
constructor(config = {}, roomRepository) {
this.config = config;
this.roomRepository = roomRepository;
}
async createRoom(roomId, options) {
throw new Error("Method not implemented");
}
async deleteRoom(roomId) {
throw new Error("Method not implemented");
}
async getRoomStatus(roomId) {
throw new Error("Method not implemented");
}
async listRooms() {
throw new Error("Method not implemented");
}
async cleanup() {
throw new Error("Method not implemented");
}
async updateRoomInfo(roomId, info) {
let room = await this.getRoomInfo(roomId);
if (!room) return false;
for (let key of Object.keys(room)) {
if (info[key] !== undefined) {
room[key] = info[key];
}
}
const result = await this.roomRepository.update(room);
return result != null;
}
async getRoomInfo(roomId) {
const info = await this.roomRepository.get(roomId);
return info;
}
}
module.exports = BaseRoomProvider;

View file

@ -1,34 +0,0 @@
import { GlideClient, GlideString } from "@valkey/valkey-glide";
import { RoomInfo, RoomOptions, BaseProviderConfig } from '../../types/room';
export abstract class BaseRoomProvider<T extends RoomInfo> {
protected valkey: GlideClient;
constructor(valkey: GlideClient, protected config: BaseProviderConfig = {}) {
this.valkey = valkey;
}
abstract createRoom(roomId: string, options?: RoomOptions): Promise<T>;
abstract deleteRoom(roomId: string): Promise<void>;
abstract getRoomStatus(roomId: string): Promise<T | null>;
abstract listRooms(): Promise<T[]>;
abstract cleanup(): Promise<void>;
protected async updateRoomInfo(roomId: string, info: Partial<RoomInfo>): Promise<boolean> {
let room = await this.getRoomInfo(roomId);
if(!room) return false;
for(let key in Object.keys(room)){
room[key]= info[key];
}
const result = await this.valkey.set(`room:${roomId}`,room as Object as GlideString);
return result != null;
}
protected async getRoomInfo(roomId: string): Promise<RoomInfo | null> {
const info = (await this.valkey.get(`room:${roomId}`)) as RoomInfo | null;
return info;
}
}

View file

@ -0,0 +1,110 @@
const cluster = require("node:cluster");
const { cpus } = require("node:os");
const BaseRoomProvider = require("./base-provider.js");
class ClusterRoomProvider extends BaseRoomProvider {
constructor(config = {}, roomRepository) {
super(config, roomRepository);
this.workers = new Map();
if (cluster.isPrimary) {
this.initializeCluster();
}
}
initializeCluster() {
const numCPUs = cpus().length;
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork();
this.handleWorkerMessages(worker);
}
cluster.on("exit", (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Starting new worker...`);
const newWorker = cluster.fork();
this.handleWorkerMessages(newWorker);
});
}
handleWorkerMessages(worker) {
worker.on("message", async (msg) => {
if (msg.type === "room_status") {
await this.updateRoomInfo(msg.roomId, {
status: msg.status,
workerId: worker.id,
lastUpdate: Date.now(),
});
}
});
}
async createRoom(roomId, options = {}) {
const workerLoads = Array.from(this.workers.entries())
.map(([id, data]) => ({
id,
rooms: data.rooms.size,
}))
.sort((a, b) => a.rooms - b.rooms);
const workerId = workerLoads[0].id;
const worker = cluster.workers[workerId];
if (!worker) {
throw new Error("No available workers");
}
worker.send({ type: "create_room", roomId, options });
const roomInfo = {
roomId,
provider: "cluster",
status: "creating",
workerId,
pid: worker.process.pid,
createdAt: Date.now(),
};
await this.updateRoomInfo(roomId, roomInfo);
return roomInfo;
}
async deleteRoom(roomId) {
const roomInfo = await this.getRoomInfo(roomId);
if (roomInfo?.workerId && cluster.workers[roomInfo.workerId]) {
cluster.workers[roomInfo.workerId].send({
type: "delete_room",
roomId,
});
}
//await this.valkey.del(["room", roomId]);
}
async getRoomStatus(roomId) {
return await this.getRoomInfo(roomId);
}
async listRooms() {
let rooms = [];
/*
const keys = await this.valkey.hkeys("room:*");
const rooms = await Promise.all(
keys.map((key) => this.getRoomInfo(key.split(":")[1]))
);
*/
return rooms.filter((room) => room !== null);
}
async cleanup() {
const rooms = await this.listRooms();
const staleTimeout = 30000;
for (const room of rooms) {
if (Date.now() - (room.lastUpdate || room.createdAt) > staleTimeout) {
await this.deleteRoom(room.roomId);
}
}
}
}
module.exports = ClusterRoomProvider;

View file

@ -1,117 +0,0 @@
import * as cluster from 'node:cluster';
import { cpus } from 'node:os';
import { GlideClient } from "@valkey/valkey-glide";
import {
ClusterRoomInfo,
RoomOptions,
ClusterProviderConfig
} from '../../types/room';
import { BaseRoomProvider } from './base-provider';
export class ClusterRoomProvider extends BaseRoomProvider<ClusterRoomInfo> {
private workers: Map<number, { rooms: Set<string> }>;
constructor(valkey: GlideClient, config: ClusterProviderConfig = {}) {
super(valkey, config);
this.workers = new Map();
if (cluster.default.isPrimary) {
this.initializeCluster();
}
}
private initializeCluster(): void {
const numCPUs = cpus().length;
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.default.fork();
this.handleWorkerMessages(worker);
}
cluster.default.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Starting new worker...`);
const newWorker = cluster.default.fork();
this.handleWorkerMessages(newWorker);
});
}
private handleWorkerMessages(worker: cluster.Worker): void {
worker.on('message', async (msg: {
type: string;
roomId: string;
status: string;
}) => {
if (msg.type === 'room_status') {
await this.updateRoomInfo(msg.roomId, {
status: msg.status as any,
workerId: worker.id,
lastUpdate: Date.now()
} as Partial<ClusterRoomInfo>);
}
});
}
async createRoom(roomId: string, options: RoomOptions = {}): Promise<ClusterRoomInfo> {
const workerLoads = Array.from(this.workers.entries())
.map(([id, data]) => ({
id,
rooms: data.rooms.size
}))
.sort((a, b) => a.rooms - b.rooms);
const workerId = workerLoads[0].id;
const worker = cluster.default.workers?.[workerId];
if (!worker) {
throw new Error('No available workers');
}
worker.send({ type: 'create_room', roomId, options });
const roomInfo: ClusterRoomInfo = {
roomId,
provider: 'cluster',
status: 'creating',
workerId,
pid: worker.process.pid!,
createdAt: Date.now()
};
await this.updateRoomInfo(roomId, roomInfo);
return roomInfo;
}
async deleteRoom(roomId: string): Promise<void> {
const roomInfo = await this.getRoomInfo(roomId) as ClusterRoomInfo;
if (roomInfo?.workerId && cluster.Worker?.[roomInfo.workerId]) {
cluster.Worker[roomInfo.workerId].send({
type: 'delete_room',
roomId
});
}
await this.valkey.del(['room',roomId]);
}
async getRoomStatus(roomId: string): Promise<ClusterRoomInfo | null> {
return await this.getRoomInfo(roomId) as ClusterRoomInfo | null;
}
async listRooms(): Promise<ClusterRoomInfo[]> {
const keys = await this.valkey.hkeys('room:*');
const rooms = await Promise.all(
keys.map(key => this.getRoomInfo(key.toString().split(':')[1]))
);
return rooms.filter((room): room is ClusterRoomInfo => room !== null);
}
async cleanup(): Promise<void> {
const rooms = await this.listRooms();
const staleTimeout = 30000; // 30 seconds
for (const room of rooms) {
if (Date.now() - (room.lastUpdate || room.createdAt) > staleTimeout) {
await this.deleteRoom(room.roomId);
}
}
}
}

View file

@ -0,0 +1,84 @@
const Docker = require("dockerode");
const { Room, RoomRepository } = require("../models/room.js");
const BaseRoomProvider = require("./base-provider.js");
class DockerRoomProvider extends BaseRoomProvider {
constructor(config, roomRepository) {
super(config, roomRepository);
this.docker = new Docker({ socketPath: "/var/run/docker.sock" });
}
async createRoom(roomId, options) {
const host = "localhost:4500";
const id = await this.roomRepository.create(
new Room(roomId, roomId, host, 0)
);
return roomRepository.get(id);
}
async deleteRoom(roomId) {
try {
const container = this.docker.getContainer(roomId);
await container.stop();
await container.remove();
await roomRepository.delete(roomId);
console.log(`Conteneur pour la salle ${roomId} supprimé.`);
} catch (error) {
console.error(
`Erreur lors de la suppression du conteneur pour la salle ${roomId}:`,
error
);
throw new Error("Failed to delete room");
}
}
async getRoomStatus(roomId) {
const room = await roomRepository.get(roomId);
if (!room) return null;
try {
const container = this.docker.getContainer(room.containerId);
const info = await container.inspect();
const updatedRoomInfo = {
...room,
status: info.State.Running ? "running" : "terminated",
containerStatus: {
Running: info.State.Running,
StartedAt: info.State.StartedAt,
FinishedAt: info.State.FinishedAt,
},
lastUpdate: Date.now(),
};
await this.roomRepository.update(updatedRoomInfo);
return updatedRoomInfo;
} catch (error) {
console.error(
`Erreur lors de la récupération du statut du conteneur pour la salle ${roomId}:`,
error
);
return null;
}
}
async listRooms() {
const rooms = await this.roomRepository.getAll();
return rooms;
}
async cleanup() {
/*
const rooms = await this.listRooms();
for (const room of rooms) {
if(room.nbStudents == 0){
await this.deleteRoom(room.roomId);
}
}
console.log("Nettoyage des salles terminé.");
*/
}
}
module.exports = DockerRoomProvider;

46
server/routers/rooms.js Normal file
View file

@ -0,0 +1,46 @@
const { Router } = require("express");
const roomsController = require('../app.js').rooms;
const jwt = require('../middleware/jwtToken.js');
const router = Router();
router.get("/listRooms", async (req, res)=> {
try {
const data = await roomsController.listRooms();
res.json(data);
} catch (error) {
res.status(500).json({ error: "Failed to list rooms" });
}
});
router.get("/createRoom", async (req, res) => {
try {
const data = await roomsController.createRoom();
res.json(data);
} catch (error) {
res.status(500).json({ error: "Failed to list rooms" });
}
});
router.get("/deleteRoom", async (req, res) => {
try {
const data = await roomsController.deleteRoom();
res.json(data);
} catch (error) {
res.status(500).json({ error: "Failed to list rooms" });
}
});
router.get("/getRoomStatus", async (req, res) => {
try {
const data = await roomsController.getRoomStatus();
res.json(data);
} catch (error) {
res.status(500).json({ error: "Failed to list rooms" });
}
});
module.exports = router;

View file

@ -1 +0,0 @@
import {express} from 'express'