mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
semi-stable-state - non-working
Co-authored-by: MathieuSevignyLavallee <MathieuSevignyLavallee@users.noreply.github.com>
This commit is contained in:
parent
bbc0359ead
commit
c26708a609
22 changed files with 2209 additions and 1671 deletions
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal 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
309
client/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
85
docker-compose.local.yaml
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
64
server/controllers/rooms.js
Normal file
64
server/controllers/rooms.js
Normal 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;
|
||||
|
|
@ -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)}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
68
server/models/room.js
Normal 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
2770
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
55
server/roomsProviders/base-provider.js
Normal file
55
server/roomsProviders/base-provider.js
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
110
server/roomsProviders/cluster-provider.js
Normal file
110
server/roomsProviders/cluster-provider.js
Normal 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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
server/roomsProviders/docker-provider.js
Normal file
84
server/roomsProviders/docker-provider.js
Normal 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
46
server/routers/rooms.js
Normal 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;
|
||||
|
|
@ -1 +0,0 @@
|
|||
import {express} from 'express'
|
||||
Loading…
Reference in a new issue