From 369cc218f7b8d8bedb3599f774569747eff80e97 Mon Sep 17 00:00:00 2001 From: NouhailaAater Date: Sun, 9 Feb 2025 16:10:25 -0500 Subject: [PATCH] Ajout de roomID --- .gitignore | 59 ++++ client/package-lock.json | 300 +++++++----------- client/package.json | 10 +- client/src/Types/QuizType.tsx | 2 +- .../StudentWaitPage/StudentWaitPage.tsx | 5 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 237 ++++++-------- server/controllers/quiz.js | 182 ++++++----- server/middleware/AppError.js | 11 +- server/models/quiz.js | 47 +-- server/routers/quiz.js | 3 +- server/socket/socket.js | 200 +++++------- 11 files changed, 509 insertions(+), 547 deletions(-) diff --git a/.gitignore b/.gitignore index 6e8de7b..67d855f 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,62 @@ dist .yarn/install-state.gz .pnp.* db-backup/ + +# Node.js / React (évite de versionner les dépendances) +node_modules/ +package-lock.json +yarn.lock +npm-debug.log +.DS_Store + +# Dossier de sortie du build (React/Vite/Webpack) +dist/ +build/ + +# Fichiers environnement (ne pas exposer tes variables sensibles) +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Visual Studio & VS Code (évite les fichiers temporaires et caches) +.vscode/ +.vs/ +*.code-workspace +*.suo +*.user +*.cache +*.log + +# Fichiers spécifiques de Visual Studio +FileContentIndex/ +*.vsidx +.vsconfig +slnx.sqlite +VSWorkspaceState.json +ProjectSettings.json +DocumentLayout*.json + +# Logs et fichiers temporaires +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Ignorer les fichiers de tests/jest +coverage/ +*.test.js +*.test.ts +*.test.tsx +jest.config.js + +# Ignorer les fichiers de dépendances frontend (si utilisés) +.bower_components/ +jspm_packages/ + +# Cache TypeScript et fichiers de sortie (évite les fichiers générés) +*.tsbuildinfo +*.tscache +*.d.ts diff --git a/client/package-lock.json b/client/package-lock.json index 3c24ffc..6753df8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,11 +14,11 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.4.1", + "@mui/icons-material": "^6.4.3", "@mui/lab": "^5.0.0-alpha.153", - "@mui/material": "^6.1.0", + "@mui/material": "^6.4.3", "@types/uuid": "^9.0.7", - "axios": "^1.6.7", + "axios": "^1.7.9", "dompurify": "^3.2.3", "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", @@ -29,9 +29,9 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-modal": "^3.16.1", - "react-router-dom": "^6.26.2", + "react-router-dom": "^6.29.0", "remark-math": "^6.0.0", - "socket.io-client": "^4.7.2", + "socket.io-client": "^4.8.1", "ts-node": "^10.9.1", "uuid": "^9.0.1", "vite-plugin-checker": "^0.8.0" @@ -2549,7 +2549,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2568,7 +2568,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2578,7 +2578,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2593,7 +2593,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2604,7 +2604,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2617,7 +2617,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2630,7 +2630,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2654,7 +2654,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2665,7 +2665,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -2678,7 +2678,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2691,7 +2691,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2701,7 +2701,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2711,7 +2711,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2818,7 +2818,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2828,7 +2828,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2842,7 +2842,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2856,7 +2856,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3355,9 +3355,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz", - "integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz", + "integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==", "license": "MIT", "funding": { "type": "opencollective", @@ -3365,9 +3365,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz", - "integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.3.tgz", + "integrity": "sha512-3IY9LpjkwIJVgL/SkZQKKCUcumdHdQEsJaIavvsQze2QEztBt0HJ17naToN0DBBdhKdtwX5xXrfD6ZFUeWWk8g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -3380,7 +3380,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.1", + "@mui/material": "^6.4.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3464,16 +3464,16 @@ } }, "node_modules/@mui/material": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz", - "integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz", + "integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.1", - "@mui/system": "^6.4.1", + "@mui/core-downloads-tracker": "^6.4.3", + "@mui/system": "^6.4.3", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.3", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -3492,7 +3492,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.1", + "@mui/material-pigment-css": "^6.4.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3513,13 +3513,13 @@ } }, "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz", - "integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", + "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.3", "prop-types": "^15.8.1" }, "engines": { @@ -3540,9 +3540,9 @@ } }, "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", - "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", + "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -3574,16 +3574,16 @@ } }, "node_modules/@mui/material/node_modules/@mui/system": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz", - "integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", + "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.1", - "@mui/styled-engine": "^6.4.0", + "@mui/private-theming": "^6.4.3", + "@mui/styled-engine": "^6.4.3", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.3", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3614,9 +3614,9 @@ } }, "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", + "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -3844,9 +3844,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", + "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -3859,7 +3859,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3873,7 +3872,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3887,7 +3885,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3901,7 +3898,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3915,7 +3911,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3929,7 +3924,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3943,7 +3937,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3957,7 +3950,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3971,7 +3963,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3985,7 +3976,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3999,7 +3989,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4013,7 +4002,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4027,7 +4015,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4041,7 +4028,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4055,7 +4041,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4069,7 +4054,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4083,7 +4067,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4097,7 +4080,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4138,7 +4120,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4180,7 +4162,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4197,7 +4178,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4214,7 +4194,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4231,7 +4210,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4248,7 +4226,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4265,7 +4242,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4282,7 +4258,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4299,7 +4274,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4316,7 +4290,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4333,7 +4306,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4347,14 +4319,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4548,7 +4520,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4648,7 +4619,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -5029,7 +5000,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5063,7 +5034,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5138,7 +5109,7 @@ "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, + "devOptional": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5317,9 +5288,9 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -5952,7 +5923,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6123,7 +6094,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6673,7 +6644,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6831,7 +6802,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6848,7 +6819,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6861,7 +6832,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6875,7 +6846,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6886,7 +6857,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6899,7 +6870,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6912,7 +6883,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6930,7 +6901,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6956,7 +6927,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6969,7 +6940,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7062,7 +7033,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7097,14 +7068,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fastq": { @@ -7130,7 +7101,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7184,7 +7155,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7201,7 +7172,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7215,7 +7186,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7450,6 +7421,7 @@ "version": "2.0.0-beta.1", "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-2.0.0-beta.1.tgz", "integrity": "sha512-NFWSu3KjpjKrfnbIu/eQOyQqjCgOd/ONDe3+bKhtTQCrTgQPVoybme9cm8tqBmJz1YynloocrPlv9f2syQl/LQ==", + "license": "MIT", "dependencies": { "pegjs": "^0.10.x" } @@ -7480,7 +7452,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7768,7 +7740,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7814,7 +7786,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8317,7 +8289,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9355,7 +9327,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9425,7 +9397,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9438,14 +9410,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json5": { @@ -9517,7 +9489,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9547,7 +9519,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9567,7 +9539,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9604,7 +9576,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10381,7 +10353,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10561,7 +10533,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10597,7 +10569,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10613,7 +10585,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10681,7 +10653,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10843,7 +10815,6 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10872,7 +10843,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -10891,7 +10861,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11084,12 +11054,12 @@ } }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz", + "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.22.0" }, "engines": { "node": ">=14.0.0" @@ -11099,13 +11069,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz", + "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.22.0", + "react-router": "6.29.0" }, "engines": { "node": ">=14.0.0" @@ -11380,7 +11350,6 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11582,7 +11551,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11595,7 +11564,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11741,7 +11710,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11972,7 +11940,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12240,7 +12208,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12352,7 +12320,6 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12586,7 +12553,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12675,7 +12642,6 @@ "version": "5.4.14", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", - "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12813,7 +12779,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12830,7 +12795,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12847,7 +12811,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12864,7 +12827,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12881,7 +12843,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12898,7 +12859,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12915,7 +12875,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12932,7 +12891,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12949,7 +12907,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12966,7 +12923,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12983,7 +12939,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13000,7 +12955,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13017,7 +12971,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13034,7 +12987,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13051,7 +13003,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13068,7 +13019,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13085,7 +13035,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13102,7 +13051,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13119,7 +13067,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13136,7 +13083,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13153,7 +13099,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13170,7 +13115,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13187,7 +13131,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13201,7 +13144,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13411,7 +13353,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13515,7 +13457,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13672,7 +13614,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/client/package.json b/client/package.json index 2a99173..a537a5c 100644 --- a/client/package.json +++ b/client/package.json @@ -18,11 +18,11 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.4.1", + "@mui/icons-material": "^6.4.3", "@mui/lab": "^5.0.0-alpha.153", - "@mui/material": "^6.1.0", + "@mui/material": "^6.4.3", "@types/uuid": "^9.0.7", - "axios": "^1.6.7", + "axios": "^1.7.9", "dompurify": "^3.2.3", "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", @@ -33,9 +33,9 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-modal": "^3.16.1", - "react-router-dom": "^6.26.2", + "react-router-dom": "^6.29.0", "remark-math": "^6.0.0", - "socket.io-client": "^4.7.2", + "socket.io-client": "^4.8.1", "ts-node": "^10.9.1", "uuid": "^9.0.1", "vite-plugin-checker": "^0.8.0" diff --git a/client/src/Types/QuizType.tsx b/client/src/Types/QuizType.tsx index b5e2b08..117e997 100644 --- a/client/src/Types/QuizType.tsx +++ b/client/src/Types/QuizType.tsx @@ -1,4 +1,3 @@ -// QuizType.tsx export interface QuizType { _id: string; folderId: string; @@ -6,6 +5,7 @@ export interface QuizType { userId: string; title: string; content: string[]; + roomId: string; created_at: Date; updated_at: Date; } diff --git a/client/src/components/StudentWaitPage/StudentWaitPage.tsx b/client/src/components/StudentWaitPage/StudentWaitPage.tsx index 9989b71..94bd506 100644 --- a/client/src/components/StudentWaitPage/StudentWaitPage.tsx +++ b/client/src/components/StudentWaitPage/StudentWaitPage.tsx @@ -10,9 +10,10 @@ interface Props { students: StudentType[]; launchQuiz: () => void; setQuizMode: (mode: 'student' | 'teacher') => void; + isSocketConnected?: boolean; } -const StudentWaitPage: React.FC = ({ students, launchQuiz, setQuizMode }) => { +const StudentWaitPage: React.FC = ({ students, launchQuiz, setQuizMode, isSocketConnected = true }) => { const [isDialogOpen, setIsDialogOpen] = useState(false); return ( @@ -25,7 +26,7 @@ const StudentWaitPage: React.FC = ({ students, launchQuiz, setQuizMode }) fullWidth sx={{ fontWeight: 600, fontSize: 20 }} > - Lancer + {isSocketConnected ? 'Lancer' : 'En attente de connexion...'} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 7af12f9..86adf5c 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -1,11 +1,9 @@ -// ManageRoom.tsx import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { Socket } from 'socket.io-client'; import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards"; import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; -// import { QuestionService } from '../../../services/QuestionService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; import { QuizType } from '../../../Types/QuizType'; import GroupIcon from '@mui/icons-material/Group'; @@ -18,14 +16,12 @@ import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle'; import { Refresh, Error } from '@mui/icons-material'; import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; -//import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation'; import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay'; import ApiService from '../../../services/ApiService'; import { QuestionType } from 'src/Types/QuestionType'; const ManageRoom: React.FC = () => { const navigate = useNavigate(); - const [roomName, setRoomName] = useState(''); const [socket, setSocket] = useState(null); const [students, setStudents] = useState([]); const quizId = useParams<{ id: string }>(); @@ -35,30 +31,39 @@ const ManageRoom: React.FC = () => { const [connectingError, setConnectingError] = useState(''); const [currentQuestion, setCurrentQuestion] = useState(undefined); const [quizStarted, setQuizStarted] = useState(false); - + const [isSocketConnected, setIsSocketConnected] = useState(false); + + useEffect(() => { if (quizId.id) { const fetchquiz = async () => { + try { + const quiz = await ApiService.getQuiz(quizId.id as string); - const quiz = await ApiService.getQuiz(quizId.id as string); + if (!quiz) { + window.alert(`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`) + console.error('Quiz not found for id:', quizId.id); + navigate('/teacher/dashboard'); + return; + } - if (!quiz) { - window.alert(`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`) - console.error('Quiz not found for id:', quizId.id); + if ((quiz as QuizType).roomId) { + setQuiz(quiz as QuizType); + + if (!socket) { + console.log('Initializing WebSocket connection...'); + initializeWebSocketConnection((quiz as QuizType).roomId); + } + } else { + console.error('Quiz data is not valid, roomId is missing.'); + navigate('/teacher/dashboard'); + } + + } catch (error) { + console.error('Failed to load quiz:', error); + setConnectingError('Erreur de chargement du quiz'); navigate('/teacher/dashboard'); - return; } - - setQuiz(quiz as QuizType); - - if (!socket) { - console.log(`no socket in ManageRoom, creating one.`); - createWebSocketRoom(); - } - - // return () => { - // webSocketService.disconnect(); - // }; }; fetchquiz(); @@ -72,74 +77,83 @@ const ManageRoom: React.FC = () => { }, [quizId]); const disconnectWebSocket = () => { - if (socket) { - webSocketService.endQuiz(roomName); + if (socket && quiz) { + webSocketService.endQuiz(quiz.roomId); webSocketService.disconnect(); setSocket(null); setQuizQuestions(undefined); setCurrentQuestion(undefined); setStudents(new Array()); - setRoomName(''); } }; - const createWebSocketRoom = () => { - console.log('Creating WebSocket room...'); - setConnectingError(''); + + const initializeWebSocketConnection = (roomId: string) => { + console.log('Initializing WebSocket for room:', roomId); const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + console.log('Socket connection status:', webSocketService.isConnected()); socket.on('connect', () => { - webSocketService.createRoom(); + console.log('WebSocket connected, joining room:', roomId); + setIsSocketConnected(true); + webSocketService.joinRoom(roomId, 'Teacher'); }); + socket.on('connect_error', (error) => { setConnectingError('Erreur lors de la connexion... Veuillez réessayer'); - console.error('ManageRoom: WebSocket connection error:', error); - }); - socket.on('create-success', (roomName: string) => { - setRoomName(roomName); - }); - socket.on('create-failure', () => { - console.log('Error creating room.'); + console.error('Connection error:', error); }); + socket.on('user-joined', (student: StudentType) => { console.log(`Student joined: name = ${student.name}, id = ${student.id}`); setStudents((prevStudents) => [...prevStudents, student]); - if (quizMode === 'teacher') { - webSocketService.nextQuestion(roomName, currentQuestion); - } else if (quizMode === 'student') { - webSocketService.launchStudentModeQuiz(roomName, quizQuestions); + if (quiz && socket.connected) { + if (quizMode === 'teacher') { + webSocketService.nextQuestion(quiz.roomId, currentQuestion); + } else if (quizMode === 'student') { + webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions); + } + } else { + console.error("Quiz data is not available or Socket is not connected."); } }); + socket.on('join-failure', (message) => { setConnectingError(message); setSocket(null); + setIsSocketConnected(false); }); + socket.on('user-disconnected', (userId: string) => { console.log(`Student left: id = ${userId}`); setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); }); + socket.on('disconnect', () => { + console.log('WebSocket disconnected'); + setIsSocketConnected(false); + }); setSocket(socket); }; useEffect(() => { // This is here to make sure the correct value is sent when user join - if (socket) { - console.log(`Listening for user-joined in room ${roomName}`); + if (socket && quiz?.roomId) { + console.log(`Listening for user-joined in room ${quiz.roomId}`); // eslint-disable-next-line @typescript-eslint/no-unused-vars socket.on('user-joined', (_student: StudentType) => { if (quizMode === 'teacher') { - webSocketService.nextQuestion(roomName, currentQuestion); + webSocketService.nextQuestion(quiz.roomId, currentQuestion); + } else if (quizMode === 'student') { - webSocketService.launchStudentModeQuiz(roomName, quizQuestions); + webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions); } }); } - if (socket) { // handle the case where user submits an answer - console.log(`Listening for submit-answer-room in room ${roomName}`); + console.log(`Listening for submit-answer-room in room ${quiz?.roomId}`); socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => { const { answer, idQuestion, idUser, username } = answerData; console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`); @@ -189,70 +203,6 @@ const ManageRoom: React.FC = () => { }, [socket, currentQuestion, quizQuestions]); - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = (answerData: answerSubmissionType) => { - // const { answer, idQuestion, username } = answerData; - // console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === username) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${username} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - - const nextQuestion = () => { if (!quizQuestions || !currentQuestion || !quiz?.content) return; @@ -261,7 +211,7 @@ const ManageRoom: React.FC = () => { if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return; setCurrentQuestion(quizQuestions[nextQuestionIndex]); - webSocketService.nextQuestion(roomName, quizQuestions[nextQuestionIndex]); + webSocketService.nextQuestion(quiz.roomId, quizQuestions[nextQuestionIndex]); }; const previousQuestion = () => { @@ -271,7 +221,7 @@ const ManageRoom: React.FC = () => { if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; setCurrentQuestion(quizQuestions[prevQuestionIndex]); - webSocketService.nextQuestion(roomName, quizQuestions[prevQuestionIndex]); + webSocketService.nextQuestion(quiz.roomId, quizQuestions[prevQuestionIndex]); }; const initializeQuizQuestion = () => { @@ -291,37 +241,37 @@ const ManageRoom: React.FC = () => { const launchTeacherMode = () => { const quizQuestions = initializeQuizQuestion(); - console.log('launchTeacherMode - quizQuestions:', quizQuestions); - - if (!quizQuestions) { - console.log('Error launching quiz (launchTeacherMode). No questions found.'); - return; + if (quizQuestions && quiz?.roomId) { + setCurrentQuestion(quizQuestions[0]); + webSocketService.nextQuestion(quiz.roomId, quizQuestions[0]); } - - setCurrentQuestion(quizQuestions[0]); - webSocketService.nextQuestion(roomName, quizQuestions[0]); }; const launchStudentMode = () => { const quizQuestions = initializeQuizQuestion(); - console.log('launchStudentMode - quizQuestions:', quizQuestions); - - if (!quizQuestions) { - console.log('Error launching quiz (launchStudentMode). No questions found.'); - return; + if (quizQuestions && quiz?.roomId) { + setQuizQuestions(quizQuestions); + webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions); } - setQuizQuestions(quizQuestions); - webSocketService.launchStudentModeQuiz(roomName, quizQuestions); }; - const launchQuiz = () => { - if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) { - // TODO: This error happens when token expires! Need to handle it properly - console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`); - setQuizStarted(true); - + const launchQuiz = async () => { + if (!isSocketConnected) { + console.log("Waiting for socket connection..."); + window.alert("En attente de la connexion au serveur... Veuillez réessayer dans quelques secondes."); return; } + + if (!quiz?.roomId) { + console.error("Room ID is missing."); + return; + } + + if (!quiz?.content || quiz.content.length === 0) { + console.error("Quiz content is missing or empty."); + return; + } + switch (quizMode) { case 'student': setQuizStarted(true); @@ -329,7 +279,9 @@ const ManageRoom: React.FC = () => { case 'teacher': setQuizStarted(true); return launchTeacherMode(); - + default: + console.error("Invalid quiz mode."); + return; } }; @@ -338,7 +290,7 @@ const ManageRoom: React.FC = () => { setCurrentQuestion(quizQuestions[questionIndex]); if (quizMode === 'teacher') { - webSocketService.nextQuestion(roomName, quizQuestions[questionIndex]); + webSocketService.nextQuestion(quiz.roomId, quizQuestions[questionIndex]); } } }; @@ -403,11 +355,11 @@ const ManageRoom: React.FC = () => { } - if (!roomName) { + if (!quiz || !quiz?.roomId) { return (
{!connectingError ? ( - + ) : (
@@ -415,7 +367,13 @@ const ManageRoom: React.FC = () => { @@ -439,7 +397,7 @@ const ManageRoom: React.FC = () => {
-
Salle: {roomName}
+
Salle: {quiz?.roomId}
{quizStarted && (
@@ -526,6 +484,7 @@ const ManageRoom: React.FC = () => { students={students} launchQuiz={launchQuiz} setQuizMode={setQuizMode} + isSocketConnected={isSocketConnected} /> )} @@ -535,4 +494,4 @@ const ManageRoom: React.FC = () => { ); }; -export default ManageRoom; +export default ManageRoom; \ No newline at end of file diff --git a/server/controllers/quiz.js b/server/controllers/quiz.js index 77f7953..d240865 100644 --- a/server/controllers/quiz.js +++ b/server/controllers/quiz.js @@ -13,169 +13,185 @@ class QuizController { create = async (req, res, next) => { try { const { title, content, folderId } = req.body; - + if (!title || !content || !folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - // Is this folder mine + const owner = await this.folders.getOwner(folderId); - if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await this.quizzes.create(title, content, folderId, req.user.userId); - + + const roomId = this.generateRoomId(); + + const result = await this.quizzes.create(title, content, folderId, req.user.userId, roomId); if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); } - + return res.status(200).json({ - message: 'Quiz créé avec succès.' + message: 'Quiz créé avec succès.', + roomId: roomId, + }); + + } catch (error) { + return next(error); + } + }; + + getRoomID = async (req, res, next) => { + try { + const { quizId } = req.params; + + if (!quizId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const roomId = await this.quizzes.getRoomID(quizId); + + if (!roomId) { + throw new AppError(QUIZ_NOT_FOUND); + } + + return res.status(200).json({ + roomId: roomId }); - } catch (error) { return next(error); } }; - get = async (req, res, next) => { try { const { quizId } = req.params; - + if (!quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + const content = await this.quizzes.getContent(quizId); - + if (!content) { throw new AppError(GETTING_QUIZ_ERROR); } - - // Is this quiz mine + if (content.userId != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - + return res.status(200).json({ data: content }); - + } catch (error) { return next(error); } }; - + delete = async (req, res, next) => { try { const { quizId } = req.params; - + if (!quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - // Is this quiz mine + const owner = await this.quizzes.getOwner(quizId); - + if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - + const result = await this.quizzes.delete(quizId); - + if (!result) { throw new AppError(DELETE_QUIZ_ERROR); } - + return res.status(200).json({ message: 'Quiz supprimé avec succès.' }); - + } catch (error) { return next(error); } }; - + update = async (req, res, next) => { try { const { quizId, newTitle, newContent } = req.body; - + if (!newTitle || !newContent || !quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - // Is this quiz mine + const owner = await this.quizzes.getOwner(quizId); - + if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - + const result = await this.quizzes.update(quizId, newTitle, newContent); - + if (!result) { throw new AppError(UPDATE_QUIZ_ERROR); } - + return res.status(200).json({ message: 'Quiz mis à jours avec succès.' }); - + } catch (error) { return next(error); } }; - + move = async (req, res, next) => { try { const { quizId, newFolderId } = req.body; - + if (!quizId || !newFolderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - // Is this quiz mine + const quizOwner = await this.quizzes.getOwner(quizId); - + if (quizOwner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - - // Is this folder mine + const folderOwner = await this.folders.getOwner(newFolderId); - + if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - + const result = await this.quizzes.move(quizId, newFolderId); - + if (!result) { throw new AppError(MOVING_QUIZ_ERROR); } - + return res.status(200).json({ message: 'Utilisateur déplacé avec succès.' }); - + } catch (error) { return next(error); } }; - + copy = async (req, _res, _next) => { const { quizId, newTitle, folderId } = req.body; - + if (!quizId || !newTitle || !folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + throw new AppError(NOT_IMPLEMENTED); // const { quizId } = req.params; // const { newUserId } = req.body; - + // try { // //Trouver le quiz a dupliquer // const conn = db.getConnection(); @@ -189,7 +205,7 @@ class QuizController { // //Ajout du duplicata // await conn.collection('quiz').insertOne({ ...quiztoduplicate, userId: ObjectId.createFromHexString(newUserId) }); // res.json(Response.ok("Dossier dupliqué avec succès pour un autre utilisateur")); - + // } catch (error) { // if (error.message.startsWith("Quiz non trouvé")) { // return res.status(404).json(Response.badRequest(error.message)); @@ -197,18 +213,18 @@ class QuizController { // res.status(500).json(Response.serverError(error.message)); // } }; - + deleteQuizzesByFolderId = async (req, res, next) => { try { const { folderId } = req.body; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Call the method from the Quiz model to delete quizzes by folder ID await this.quizzes.deleteQuizzesByFolderId(folderId); - + return res.status(200).json({ message: 'Quizzes deleted successfully.' }); @@ -216,10 +232,10 @@ class QuizController { return next(error); } }; - + duplicate = async (req, res, next) => { const { quizId } = req.body; - + try { const newQuizId = await this.quizzes.duplicate(quizId, req.user.userId); res.status(200).json({ success: true, newQuizId }); @@ -227,7 +243,7 @@ class QuizController { return next(error); } }; - + quizExists = async (title, userId) => { try { const existingFile = await this.quizzes.quizExists(title, userId); @@ -236,82 +252,90 @@ class QuizController { throw new AppError(GETTING_QUIZ_ERROR); } }; - + share = async (req, res, next) => { try { const { quizId, email } = req.body; - + if (!quizId || !email) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`; - + emailer.quizShare(email, link); - + return res.status(200).json({ message: 'Quiz partagé avec succès.' }); - + } catch (error) { return next(error); } }; - + getShare = async (req, res, next) => { try { const { quizId } = req.params; - + if (!quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + const content = await this.quizzes.getContent(quizId); - + if (!content) { throw new AppError(GETTING_QUIZ_ERROR); } - + return res.status(200).json({ data: content.title }); - + } catch (error) { return next(error); } }; - + receiveShare = async (req, res, next) => { try { const { quizId, folderId } = req.body; - + if (!quizId || !folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + const folderOwner = await this.folders.getOwner(folderId); if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - + const content = await this.quizzes.getContent(quizId); if (!content) { throw new AppError(GETTING_QUIZ_ERROR); } - + const result = await this.quizzes.create(content.title, content.content, folderId, req.user.userId); if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); } - + return res.status(200).json({ message: 'Quiz partagé reçu.' }); } catch (error) { return next(error); } - }; + }; + generateRoomId(length = 6) { + const characters = "0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; + } } module.exports = QuizController; diff --git a/server/middleware/AppError.js b/server/middleware/AppError.js index 58a4d83..70024ef 100644 --- a/server/middleware/AppError.js +++ b/server/middleware/AppError.js @@ -1,9 +1,10 @@ class AppError extends Error { - constructor (errorCode) { - super(errorCode.message) - this.statusCode = errorCode.code; - this.isOperational = true; // Optional: to distinguish operational errors from programming errors + constructor(errorCode = { message: "Something went wrong", code: 500 }) { + super(errorCode.message); + this.statusCode = errorCode.code || 500; + this.isOperational = true; + Error.captureStackTrace(this, this.constructor); } } -module.exports = AppError; +module.exports = AppError; \ No newline at end of file diff --git a/server/models/quiz.js b/server/models/quiz.js index b388659..15891ce 100644 --- a/server/models/quiz.js +++ b/server/models/quiz.js @@ -2,20 +2,21 @@ const { ObjectId } = require('mongodb'); const { generateUniqueTitle } = require('./utils'); class Quiz { - constructor(db) { - // console.log("Quiz constructor: db", db) this.db = db; } - async create(title, content, folderId, userId) { - // console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`); + + async create(title, content, folderId, userId, roomId) { await this.db.connect() const conn = this.db.getConnection(); - const quizCollection = conn.collection('files'); - const existingQuiz = await quizCollection.findOne({ title: title, folderId: folderId, userId: userId }) + const existingQuiz = await quizCollection.findOne({ + title: title, + folderId: folderId, + userId: userId + }); if (existingQuiz) { throw new Error(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`); @@ -26,13 +27,12 @@ class Quiz { userId: userId, title: title, content: content, + roomId: roomId, created_at: new Date(), updated_at: new Date() } const result = await quizCollection.insertOne(newQuiz); - // console.log("quizzes: create insertOne result", result); - return result.insertedId; } @@ -58,6 +58,14 @@ class Quiz { return quiz; } + async getRoomID(quizId) { + await this.db.connect() + const conn = this.db.getConnection(); + const quizCollection = conn.collection('files'); + const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) }); + return quiz.roomId; + } + async delete(quizId) { await this.db.connect() const conn = this.db.getConnection(); @@ -89,12 +97,12 @@ class Quiz { const result = await quizCollection.updateOne( { _id: ObjectId.createFromHexString(quizId) }, - { + { $set: { - title: newTitle, - content: newContent, - updated_at: new Date() - } + title: newTitle, + content: newContent, + updated_at: new Date() + } } ); @@ -108,7 +116,7 @@ class Quiz { const quizCollection = conn.collection('files'); const result = await quizCollection.updateOne( - { _id: ObjectId.createFromHexString(quizId) }, + { _id: ObjectId.createFromHexString(quizId) }, { $set: { folderId: newFolderId } } ); @@ -143,13 +151,12 @@ class Quiz { async quizExists(title, userId) { await this.db.connect(); const conn = this.db.getConnection(); - - const filesCollection = conn.collection('files'); - const existingFolder = await filesCollection.findOne({ title: title, userId: userId }); - + + const filesCollection = conn.collection('files'); + const existingFolder = await filesCollection.findOne({ title: title, userId: userId }); + return existingFolder !== null; } - } -module.exports = Quiz; +module.exports = Quiz; \ No newline at end of file diff --git a/server/routers/quiz.js b/server/routers/quiz.js index 0139c7b..17a2953 100644 --- a/server/routers/quiz.js +++ b/server/routers/quiz.js @@ -10,6 +10,7 @@ if (!quizzes) { router.post("/create", jwt.authenticate, asyncHandler(quizzes.create)); router.get("/get/:quizId", jwt.authenticate, asyncHandler(asyncHandler(quizzes.get))); +router.get('/getRoomID/:quizId', jwt.authenticate, asyncHandler(quizzes.getRoomID)); router.delete("/delete/:quizId", jwt.authenticate, asyncHandler(quizzes.delete)); router.put("/update", jwt.authenticate, asyncHandler(quizzes.update)); router.put("/move", jwt.authenticate, asyncHandler(quizzes.move)); @@ -20,4 +21,4 @@ router.put("/Share", jwt.authenticate, asyncHandler(quizzes.share)); router.get("/getShare/:quizId", jwt.authenticate, asyncHandler(quizzes.getShare)); router.post("/receiveShare", jwt.authenticate, asyncHandler(quizzes.receiveShare)); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/server/socket/socket.js b/server/socket/socket.js index fc21632..0787d1f 100644 --- a/server/socket/socket.js +++ b/server/socket/socket.js @@ -2,124 +2,92 @@ const MAX_USERS_PER_ROOM = 60; const MAX_TOTAL_CONNECTIONS = 2000; const setupWebsocket = (io) => { - let totalConnections = 0; + let totalConnections = 0; - io.on("connection", (socket) => { - if (totalConnections >= MAX_TOTAL_CONNECTIONS) { - console.log("Connection limit reached. Disconnecting client."); - socket.emit( - "join-failure", - "Le nombre maximum de connexions a été atteint" - ); - socket.disconnect(true); - return; - } - - totalConnections++; - console.log( - "A user connected:", - socket.id, - "| Total connections:", - totalConnections - ); - - socket.on("create-room", (sentRoomName) => { - if (sentRoomName) { - const roomName = sentRoomName.toUpperCase(); - if (!io.sockets.adapter.rooms.get(roomName)) { - socket.join(roomName); - socket.emit("create-success", roomName); - } else { - socket.emit("create-failure"); + io.on("connection", (socket) => { + if (totalConnections >= MAX_TOTAL_CONNECTIONS) { + console.log("Connection limit reached. Disconnecting client."); + socket.emit( + "join-failure", + "Le nombre maximum de connexions a été atteint" + ); + socket.disconnect(true); + return; } - } else { - const roomName = generateRoomName(); - if (!io.sockets.adapter.rooms.get(roomName)) { - socket.join(roomName); - socket.emit("create-success", roomName); - } else { - socket.emit("create-failure"); - } - } + + totalConnections++; + console.log( + "A user connected:", + socket.id, + "| Total connections:", + totalConnections + ); + + socket.on("join-room", ({ enteredRoomName, username }) => { + if (io.sockets.adapter.rooms.has(enteredRoomName)) { + const clientsInRoom = + io.sockets.adapter.rooms.get(enteredRoomName).size; + + if (clientsInRoom <= MAX_USERS_PER_ROOM) { + const newStudent = { + id: socket.id, + name: username, + answers: [], + }; + socket.join(enteredRoomName); + socket + .to(enteredRoomName) + .emit("user-joined", newStudent); + socket.emit("join-success"); + } else { + socket.emit("join-failure", "La salle est remplie"); + } + } else { + socket.emit("join-failure", "Le nom de la salle n'existe pas"); + } + }); + + socket.on("next-question", ({ roomName, question }) => { + socket.to(roomName).emit("next-question", question); + }); + + socket.on("launch-student-mode", ({ roomName, questions }) => { + socket.to(roomName).emit("launch-student-mode", questions); + }); + + socket.on("end-quiz", ({ roomName }) => { + socket.to(roomName).emit("end-quiz"); + }); + + socket.on("message", (data) => { + console.log("Received message from", socket.id, ":", data); + }); + + socket.on("disconnect", () => { + totalConnections--; + console.log( + "A user disconnected:", + socket.id, + "| Total connections:", + totalConnections + ); + + for (const [room] of io.sockets.adapter.rooms) { + if (room !== socket.id) { + io.to(room).emit("user-disconnected", socket.id); + } + } + }); + + socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => { + socket.to(roomName).emit("submit-answer-room", { + idUser: socket.id, + username, + answer, + idQuestion, + }); + }); }); - - socket.on("join-room", ({ enteredRoomName, username }) => { - if (io.sockets.adapter.rooms.has(enteredRoomName)) { - const clientsInRoom = - io.sockets.adapter.rooms.get(enteredRoomName).size; - - if (clientsInRoom <= MAX_USERS_PER_ROOM) { - const newStudent = { - id: socket.id, - name: username, - answers: [], - }; - socket.join(enteredRoomName); - socket - .to(enteredRoomName) - .emit("user-joined", newStudent); - socket.emit("join-success"); - } else { - socket.emit("join-failure", "La salle est remplie"); - } - } else { - socket.emit("join-failure", "Le nom de la salle n'existe pas"); - } - }); - - socket.on("next-question", ({ roomName, question }) => { - // console.log("next-question", roomName, question); - socket.to(roomName).emit("next-question", question); - }); - - socket.on("launch-student-mode", ({ roomName, questions }) => { - socket.to(roomName).emit("launch-student-mode", questions); - }); - - socket.on("end-quiz", ({ roomName }) => { - socket.to(roomName).emit("end-quiz"); - }); - - socket.on("message", (data) => { - console.log("Received message from", socket.id, ":", data); - }); - - socket.on("disconnect", () => { - totalConnections--; - console.log( - "A user disconnected:", - socket.id, - "| Total connections:", - totalConnections - ); - - for (const [room] of io.sockets.adapter.rooms) { - if (room !== socket.id) { - io.to(room).emit("user-disconnected", socket.id); - } - } - }); - - socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => { - socket.to(roomName).emit("submit-answer-room", { - idUser: socket.id, - username, - answer, - idQuestion, - }); - }); - }); - - const generateRoomName = (length = 6) => { - const characters = "0123456789"; - let result = ""; - for (let i = 0; i < length; i++) { - result += characters.charAt( - Math.floor(Math.random() * characters.length) - ); - } - return result; - }; }; -module.exports = { setupWebsocket }; +module.exports = { setupWebsocket }; \ No newline at end of file