From 2dc2974f0ad7c26d845e802034d089d2be4b0ff7 Mon Sep 17 00:00:00 2001 From: Eddi3_As Date: Mon, 27 Jan 2025 17:38:07 -0500 Subject: [PATCH] PFEH2025 - merge dev PFEA2024 it2-it3-it4 SSO available --- .github/workflows/create-branch-images.yml | 31 + .github/workflows/create-docs.yml | 33 + .github/workflows/tests.yml | 32 + .gitignore | 16 +- .vscode/launch.json | 16 +- ansible/Dockerfile | 10 + ansible/README.md | 40 + ansible/deploy.yml | 38 + ansible/docker-compose.yaml | 70 + ansible/inventory.ini | 9 + client/Dockerfile | 6 +- client/package-lock.json | 309 +- client/package.json | 1 + .../services/WebsocketService.test.tsx | 2 +- .../src/pages/Student/JoinRoom/JoinRoom.tsx | 6 +- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 14 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 20 +- client/src/services/ApiService.tsx | 78 +- client/src/services/WebsocketService.tsx | 17 +- create-branch-image.bat | 74 + docker-compose.local.yaml | 137 + docker-compose.yaml | 30 + documentation/.gitignore | 1 + documentation/deploy.py | 2 + documentation/docs/developpeur/backend/api.md | 12 + .../docs/developpeur/backend/auth.md | 384 +++ .../developpeur/backend/base-de-donnees.md | 11 + .../docs/developpeur/backend/katex.md | 43 + .../docs/developpeur/backend/quiz.md | 54 + .../backend/salle-de-quiz-swagger.json | 146 + .../docs/developpeur/backend/salle-de-quiz.md | 193 ++ .../docs/developpeur/deploiements/ansible.md | 77 + .../docs/developpeur/deploiements/local.md | 63 + .../docs/developpeur/deploiements/opentofu.md | 61 + .../docs/developpeur/deploiements/prod.md | 230 ++ .../developpeur/documentation/a-propos.md | 28 + .../docs/developpeur/frontend/index.md | 22 + documentation/docs/developpeur/index.md | 83 + .../developpeur/test/test-charge-output.png | Bin 0 -> 53828 bytes .../docs/developpeur/test/test-de-charge.md | 79 + documentation/docs/index.md | 23 + documentation/docs/javascripts/katex.js | 10 + .../docs/utilisateur/configuration.md | 84 + documentation/docs/utilisateur/deploiment.md | 18 + documentation/mkdocs.yml | 79 + documentation/python-version | 1 + documentation/requirements.txt | 7 + nginx/.env.example | 5 + nginx/Dockerfile | 91 +- nginx/default.conf | 31 - nginx/entrypoint.sh | 15 + nginx/njs/main.js | 97 + nginx/templates/default.conf | 81 + opentofu/README.md | 44 + opentofu/auth_config.json.example | 35 + opentofu/azure/app.tf | 67 + opentofu/azure/database.tf | 43 + opentofu/azure/main.tf | 14 + opentofu/azure/network.tf | 87 + opentofu/azure/resource_group.tf | 5 + opentofu/azure/storage.tf | 74 + opentofu/azure/terraform.tfvars.example | 7 + opentofu/azure/variables.tf | 214 ++ opentofu/docker-compose.yaml | 80 + quizRoom/.dockerignore | 2 + quizRoom/Dockerfile | 32 + quizRoom/app.ts | 57 + quizRoom/docker-compose.yml | 21 + quizRoom/healthcheck.sh | 2 + quizRoom/package-lock.json | 1595 ++++++++++ quizRoom/package.json | 27 + quizRoom/socket/.env.example | 2 + quizRoom/socket/setupWebSocket.ts | 242 ++ quizRoom/tsconfig.json | 14 + server/Dockerfile | 6 +- server/__mocks__/AppError.js | 8 + server/__mocks__/bcrypt.js | 6 + server/__mocks__/db.js | 20 + server/__mocks__/folders.js | 8 + server/__tests__/folders.test.js | 441 +++ server/__tests__/image.test.js | 16 +- server/__tests__/quizzes.test.js | 347 +++ server/__tests__/socket.test.js | 3 +- server/__tests__/users.test.js | 86 + server/app.js | 81 +- server/controllers/folders.js | 195 +- server/controllers/images.js | 45 +- server/controllers/quiz.js | 249 +- server/controllers/rooms.js | 94 + server/controllers/users.js | 29 +- server/middleware/jwtToken.js | 2 +- server/models/folders.js | 117 +- server/models/images.js | 10 +- server/models/quiz.js | 103 +- server/models/room.js | 87 + server/models/users.js | 11 +- server/models/utils.js | 35 + server/package-lock.json | 2707 ++++++++++------- server/package.json | 12 +- server/roomsProviders/base-provider.js | 75 + server/roomsProviders/cluster-provider.js | 110 + server/roomsProviders/docker-provider.js | 275 ++ server/routers/folders.js | 24 +- server/routers/health.js | 20 + server/routers/images.js | 8 +- server/routers/quiz.js | 29 +- server/routers/rooms.js | 54 + server/routers/users.js | 7 +- server/socket/socket.js | 125 - test/stressTest/.dockerignore | 2 + test/stressTest/.env.example | 16 + test/stressTest/Dockerfile | 13 + test/stressTest/README.md | 51 + test/stressTest/class/metrics.js | 46 + test/stressTest/class/roomParticipant.js | 83 + test/stressTest/class/student.js | 48 + test/stressTest/class/teacher.js | 46 + test/stressTest/class/watcher.js | 72 + test/stressTest/docker-compose.yml | 26 + test/stressTest/main.js | 201 ++ test/stressTest/package-lock.json | 1230 ++++++++ test/stressTest/package.json | 22 + test/stressTest/utility/apiServices.js | 80 + test/stressTest/utility/metrics_generator.js | 253 ++ types/room.ts | 69 + types/valkey.ts | 9 + 126 files changed, 11627 insertions(+), 1769 deletions(-) create mode 100644 .github/workflows/create-docs.yml create mode 100644 .github/workflows/tests.yml create mode 100644 ansible/Dockerfile create mode 100644 ansible/README.md create mode 100644 ansible/deploy.yml create mode 100644 ansible/docker-compose.yaml create mode 100644 ansible/inventory.ini create mode 100644 create-branch-image.bat create mode 100644 docker-compose.local.yaml create mode 100644 documentation/.gitignore create mode 100644 documentation/deploy.py create mode 100644 documentation/docs/developpeur/backend/api.md create mode 100644 documentation/docs/developpeur/backend/auth.md create mode 100644 documentation/docs/developpeur/backend/base-de-donnees.md create mode 100644 documentation/docs/developpeur/backend/katex.md create mode 100644 documentation/docs/developpeur/backend/quiz.md create mode 100644 documentation/docs/developpeur/backend/salle-de-quiz-swagger.json create mode 100644 documentation/docs/developpeur/backend/salle-de-quiz.md create mode 100644 documentation/docs/developpeur/deploiements/ansible.md create mode 100644 documentation/docs/developpeur/deploiements/local.md create mode 100644 documentation/docs/developpeur/deploiements/opentofu.md create mode 100644 documentation/docs/developpeur/deploiements/prod.md create mode 100644 documentation/docs/developpeur/documentation/a-propos.md create mode 100644 documentation/docs/developpeur/frontend/index.md create mode 100644 documentation/docs/developpeur/index.md create mode 100644 documentation/docs/developpeur/test/test-charge-output.png create mode 100644 documentation/docs/developpeur/test/test-de-charge.md create mode 100644 documentation/docs/index.md create mode 100644 documentation/docs/javascripts/katex.js create mode 100644 documentation/docs/utilisateur/configuration.md create mode 100644 documentation/docs/utilisateur/deploiment.md create mode 100644 documentation/mkdocs.yml create mode 100644 documentation/python-version create mode 100644 documentation/requirements.txt create mode 100644 nginx/.env.example delete mode 100644 nginx/default.conf create mode 100644 nginx/entrypoint.sh create mode 100644 nginx/njs/main.js create mode 100644 nginx/templates/default.conf create mode 100644 opentofu/README.md create mode 100644 opentofu/auth_config.json.example create mode 100644 opentofu/azure/app.tf create mode 100644 opentofu/azure/database.tf create mode 100644 opentofu/azure/main.tf create mode 100644 opentofu/azure/network.tf create mode 100644 opentofu/azure/resource_group.tf create mode 100644 opentofu/azure/storage.tf create mode 100644 opentofu/azure/terraform.tfvars.example create mode 100644 opentofu/azure/variables.tf create mode 100644 opentofu/docker-compose.yaml create mode 100644 quizRoom/.dockerignore create mode 100644 quizRoom/Dockerfile create mode 100644 quizRoom/app.ts create mode 100644 quizRoom/docker-compose.yml create mode 100644 quizRoom/healthcheck.sh create mode 100644 quizRoom/package-lock.json create mode 100644 quizRoom/package.json create mode 100644 quizRoom/socket/.env.example create mode 100644 quizRoom/socket/setupWebSocket.ts create mode 100644 quizRoom/tsconfig.json create mode 100644 server/__mocks__/AppError.js create mode 100644 server/__mocks__/bcrypt.js create mode 100644 server/__mocks__/db.js create mode 100644 server/__mocks__/folders.js create mode 100644 server/__tests__/folders.test.js create mode 100644 server/__tests__/quizzes.test.js create mode 100644 server/__tests__/users.test.js create mode 100644 server/controllers/rooms.js create mode 100644 server/models/room.js create mode 100644 server/models/utils.js create mode 100644 server/roomsProviders/base-provider.js create mode 100644 server/roomsProviders/cluster-provider.js create mode 100644 server/roomsProviders/docker-provider.js create mode 100644 server/routers/health.js create mode 100644 server/routers/rooms.js delete mode 100644 server/socket/socket.js create mode 100644 test/stressTest/.dockerignore create mode 100644 test/stressTest/.env.example create mode 100644 test/stressTest/Dockerfile create mode 100644 test/stressTest/README.md create mode 100644 test/stressTest/class/metrics.js create mode 100644 test/stressTest/class/roomParticipant.js create mode 100644 test/stressTest/class/student.js create mode 100644 test/stressTest/class/teacher.js create mode 100644 test/stressTest/class/watcher.js create mode 100644 test/stressTest/docker-compose.yml create mode 100644 test/stressTest/main.js create mode 100644 test/stressTest/package-lock.json create mode 100644 test/stressTest/package.json create mode 100644 test/stressTest/utility/apiServices.js create mode 100644 test/stressTest/utility/metrics_generator.js create mode 100644 types/room.ts create mode 100644 types/valkey.ts diff --git a/.github/workflows/create-branch-images.yml b/.github/workflows/create-branch-images.yml index 8b3c7bc..e4dfd15 100644 --- a/.github/workflows/create-branch-images.yml +++ b/.github/workflows/create-branch-images.yml @@ -103,4 +103,35 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha + cache-to: type=gha,mode=max + build-quizroom: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata for Quizroom Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }}-quizroom + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + - name: Build and push Quizroom Docker image + uses: docker/build-push-action@v5 + with: + context: ./quizRoom + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/create-docs.yml b/.github/workflows/create-docs.yml new file mode 100644 index 0000000..b981713 --- /dev/null +++ b/.github/workflows/create-docs.yml @@ -0,0 +1,33 @@ +name: Creates docs and deploy to gh-pages +on: + workflow_call: + workflow_dispatch: + push: + branches: [ main ] + +jobs: + build: + name: Deploy docs + runs-on: ubuntu-latest + env: + PUMLURL: "https://www.plantuml.com/plantuml/" + steps: + - name: Checkout main + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + + - name: Install dependencies + working-directory: ./documentation + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build docs + working-directory: ./documentation + run: mkdocs build --verbose --clean + + - name: Push docs to gh-pages + working-directory: ./documentation + run: python deploy.py \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..72d1741 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +name: Tests + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - name: Check Out Repo + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Dependencies and Run Tests + run: | + npm ci + npm test + working-directory: ${{ matrix.directory }} + + strategy: + matrix: + directory: [client, server] diff --git a/.gitignore b/.gitignore index c6bba59..fc43433 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,7 @@ web_modules/ .yarn-integrity # dotenv environment variable files -.env +server/.env .env.development.local .env.test.local .env.production.local @@ -128,3 +128,17 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +db-backup/ + +**/.env +.venv +deployments +/test/stressTest/output + +# Opentofu state +opentofu/*/.terraform +opentofu/*/.terraform.lock* +opentofu/*/terraform.tfstate* +opentofu/*/terraform.tfvars +# Opentofu auth config +opentofu/auth_config.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 3f9be17..9559026 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,6 +20,20 @@ "name": "Debug frontend", "url": "http://localhost:5173", "webRoot": "${workspaceFolder}/client/" - } + }, + { + "name": "Docker: Attach to Node", + "type": "node", + "request": "attach", + "restart": true, + "port": 9229, + "address": "localhost", + "localRoot": "${workspaceFolder}", + "remoteRoot": "/app", + "protocol": "inspector", + "skipFiles": [ + "/**" + ] + } ] } \ No newline at end of file diff --git a/ansible/Dockerfile b/ansible/Dockerfile new file mode 100644 index 0000000..f001708 --- /dev/null +++ b/ansible/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9-slim + +# Installer Ansible +RUN pip install ansible + +# Définir le répertoire de travail +WORKDIR /ansible + +# Copier les fichiers nécessaires +COPY inventory.ini deploy.yml ./ diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..5b6e78a --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,40 @@ +# Déploiement de Services avec Ansible et Docker Compose + +Ce guide explique comment utiliser Ansible pour configurer et déployer des services Docker avec `docker-compose`. + +## Prérequis + +1. **Ansible** : Assurez-vous qu'Ansible est installé sur votre système. + - [Guide d'installation d'Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) + +2. **Docker et Docker Compose** : Docker doit être installé et configuré pour fonctionner avec Ansible. + - Installez Docker : [Documentation Docker](https://docs.docker.com/get-docker/) + - Docker Compose est inclus comme plugin Docker dans les versions récentes de Docker. + +3. **WSL (pour Windows)** : Si vous utilisez Windows, assurez-vous d'avoir configuré WSL et un environnement Ubuntu. + +## Structure du projet + +Le fichier `deploy.yml` contient les tâches Ansible nécessaires pour télécharger, configurer, et démarrer les services Docker en utilisant Docker Compose. + +## Installation et de déploiement + +### Lancer le déploiement avec Ansible + +Pour exécuter le playbook Ansible `deploy.yml`, utilisez la commande suivante depuis le répertoire racine du projet : + +`ansible-playbook -i inventory.ini deploy.yml` + +### Vérification du déploiement + +Une fois le playbook exécuté, Ansible télécharge Docker et Docker Compose, télécharge le fichier `docker-compose.yaml`, démarre Docker et lance les conteneurs spécifiés. + +### Configuration et contenu du Playbook (deploy.yml) +Le playbook deploy.yml exécute les étapes suivantes : + +1. Télécharge Docker Compose si ce dernier n'est pas encore présent. +2. Vérifie l'installation de Docker Compose pour s'assurer qu'il est opérationnel. +3. Démarre le service Docker si ce n'est pas déjà le cas. +4. Télécharge le fichier docker-compose.yaml depuis le dépôt Git spécifié. +5. Lance Docker Compose pour déployer les conteneurs définis dans docker-compose.yaml. +6. Vérifie l'état des conteneurs et affiche les conteneurs en cours d'exécution. \ No newline at end of file diff --git a/ansible/deploy.yml b/ansible/deploy.yml new file mode 100644 index 0000000..c9f7df8 --- /dev/null +++ b/ansible/deploy.yml @@ -0,0 +1,38 @@ +--- + - name: Déployer des services avec Docker Compose + hosts: local + tasks: + + - name: Télécharger Docker + ansible.builtin.package: + name: docker-compose + state: present + + - name: Vérifier l'installation de Docker Compose plugin + ansible.builtin.command: + cmd: docker compose version + + - name: Commencer le service docker + ansible.builtin.service: + name: docker + state: started + enabled: yes + + - name: Telecharger le fichier docker-compose + ansible.builtin.get_url: + url: https://raw.githubusercontent.com/ets-cfuhrman-pfe/EvalueTonSavoir/refs/heads/main/docker-compose.yaml + dest: ./docker-compose.yaml + + - name: Lancer Docker Compose + ansible.builtin.shell: + docker-compose up -d + become: true + + - name: Vérification des services Docker + ansible.builtin.command: + cmd: docker ps + register: docker_ps_output + + - name: Afficher l'état des conteneurs Docker + ansible.builtin.debug: + msg: "{{ docker_ps_output.stdout }}" \ No newline at end of file diff --git a/ansible/docker-compose.yaml b/ansible/docker-compose.yaml new file mode 100644 index 0000000..97c112f --- /dev/null +++ b/ansible/docker-compose.yaml @@ -0,0 +1,70 @@ +services: + + frontend: + image: fuhrmanator/evaluetonsavoir-frontend:latest + container_name: frontend + ports: + - "5173:5173" + environment: + VITE_BACKEND_URL: "http://localhost:4400" + # don't define VITE_BACKEND_SOCKET_URL so it will default to window.location.host + # VITE_BACKEND_SOCKET_URL: "" + restart: always + + backend: + image: fuhrmanator/evaluetonsavoir-backend:latest + 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 + + # Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application + nginx: + image: fuhrmanator/evaluetonsavoir-routeur:latest + 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 diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..e250157 --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,9 @@ +# Spécifier les serveurs où vous souhaitez déployer votre application. + # Remplacez votre_ip_serveur par l’adresse IP de votre serveur, et votre_utilisateur_ssh par le nom d’utilisateur SSH. + +# Pour les serveurs +# [app_servers] +# votre_ip_serveur ansible_user=votre_utilisateur_ssh + +[local] +localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3 \ No newline at end of file diff --git a/client/Dockerfile b/client/Dockerfile index f1021e6..32d487b 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -12,6 +12,10 @@ RUN npm install RUN npm run build -EXPOSE 5173 +ENV PORT=5173 +EXPOSE ${PORT} + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:${PORT} || exit 1 CMD [ "npm", "run", "preview" ] \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 666c61e..50bad1b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -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", @@ -1898,6 +1899,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", @@ -5191,6 +5198,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", @@ -5459,6 +5475,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", @@ -5470,6 +5515,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", @@ -5543,12 +5599,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", @@ -5659,6 +5748,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", @@ -5787,6 +5882,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", @@ -6058,6 +6167,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", @@ -6124,6 +6262,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", @@ -6798,6 +6945,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", @@ -7079,6 +7232,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", @@ -7154,8 +7327,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", @@ -10217,11 +10389,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", @@ -10293,7 +10478,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" } @@ -10670,6 +10854,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", @@ -10814,6 +11008,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", @@ -11056,6 +11264,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", @@ -11191,12 +11419,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", @@ -11216,6 +11467,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", @@ -11328,6 +11588,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", @@ -11548,6 +11836,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", @@ -11774,6 +12068,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", @@ -12646,8 +12946,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", diff --git a/client/package.json b/client/package.json index 8f2a8ad..1039fd2 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/__tests__/services/WebsocketService.test.tsx b/client/src/__tests__/services/WebsocketService.test.tsx index e61c994..65cd3cd 100644 --- a/client/src/__tests__/services/WebsocketService.test.tsx +++ b/client/src/__tests__/services/WebsocketService.test.tsx @@ -44,7 +44,7 @@ describe('WebSocketService', () => { test('createRoom should emit create-room event', () => { WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); - WebsocketService.createRoom(); + WebsocketService.createRoom('test'); expect(mockSocket.emit).toHaveBeenCalledWith('create-room'); }); diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index 75516b0..6882fb9 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -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'; @@ -29,14 +29,14 @@ const JoinRoom: React.FC = () => { const [isConnecting, setIsConnecting] = useState(false); useEffect(() => { - handleCreateSocket(); + //handleCreateSocket(); return () => { disconnect(); }; }, []); const handleCreateSocket = () => { - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); + const socket = webSocketService.connect(`/api/room/${roomName}/socket`); socket.on('join-success', () => { setIsWaitingForTeacher(true); diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index 46fa73e..5551fce 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -98,8 +98,9 @@ const Dashboard: React.FC = () => { setQuizzes(quizzes as QuizType[]); } else { - console.log("show some quizes") + console.log("show some quizzes") const folderQuizzes = await ApiService.getFolderContent(selectedFolder); + console.log("folderQuizzes: ", folderQuizzes); setQuizzes(folderQuizzes as QuizType[]); } @@ -147,7 +148,7 @@ const Dashboard: React.FC = () => { setQuizzes(quizzes as QuizType[]); } else { - console.log("show some quizes") + console.log("show some quizzes") const folderQuizzes = await ApiService.getFolderContent(selectedFolder); setQuizzes(folderQuizzes as QuizType[]); @@ -292,7 +293,7 @@ const Dashboard: React.FC = () => { } setQuizzes(quizzes as QuizType[]); - + setSelectedFolder(''); } catch (error) { console.error('Error deleting folder:', error); @@ -317,9 +318,11 @@ const Dashboard: React.FC = () => { try { // folderId: string GET THIS FROM CURRENT FOLDER await ApiService.duplicateFolder(selectedFolder); + // TODO set the selected folder to be the duplicated folder const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); - + const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType; + setSelectedFolder(newlyCreatedFolder._id); } catch (error) { console.error('Error duplicating folder:', error); } @@ -401,7 +404,6 @@ const Dashboard: React.FC = () => {
- { > - + { } }; - const createWebSocketRoom = () => { + const createWebSocketRoom = async () => { setConnectingError(''); - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); + const room = await ApiService.createRoom(); + const socket = webSocketService.connect(`/api/room/${room.id}/socket`); socket.on('connect', () => { - webSocketService.createRoom(); + webSocketService.createRoom(room.id); }); + + socket.on("error", (error) => { + console.error("WebSocket server error:", error); + }); + socket.on('connect_error', (error) => { setConnectingError('Erreur lors de la connexion... Veuillez réessayer'); console.error('WebSocket connection error:', error); @@ -142,7 +148,7 @@ const ManageRoom: React.FC = () => { console.log('Quiz questions not found (cannot update answers without them).'); return; } - + // Update the students state using the functional form of setStudents setStudents((prevStudents) => { // print the list of current student names @@ -150,7 +156,7 @@ const ManageRoom: React.FC = () => { prevStudents.forEach((student) => { console.log(student.name); }); - + let foundStudent = false; const updatedStudents = prevStudents.map((student) => { console.log(`Comparing ${student.id} to ${idUser}`); @@ -170,7 +176,7 @@ const ManageRoom: React.FC = () => { updatedAnswers = [...student.answers, newAnswer]; } return { ...student, answers: updatedAnswers }; - } + } return student; }); if (!foundStudent) { diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index b63bbdd..12a2a74 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -145,6 +145,78 @@ class ApiService { return localStorage.removeItem("jwt"); } + + //Socket Route + + /** + * Creates a new room. + * @returns The room object if successful + * @returns An error string if unsuccessful + */ + public async createRoom(): Promise { + try { + const url: string = this.constructRequestUrl(`/room`); + const headers = this.constructRequestHeaders(); + + const response = await fetch(url, { + method: 'POST', + headers: headers, + }); + + if (!response.ok) { + throw new Error(`La création de la salle a échoué. Status: ${response.status}`); + } + + const room = await response.json(); + return room; + + } catch (error) { + console.log("Error details: ", error); + + if (error instanceof Error) { + return error.message || 'Erreur serveur inconnue lors de la requête.'; + } + + return `Une erreur inattendue s'est produite.`; + } + } + + + /** + * Deletes a room by its name. + * @param roomName - The name of the room to delete. + * @returns true if successful + * @returns An error string if unsuccessful + */ + public async deleteRoom(roomName: string): Promise { + try { + if (!roomName) { + throw new Error(`Le nom de la salle est requis.`); + } + + const url = this.constructRequestUrl(`/room/${roomName}`); + const headers = this.constructRequestHeaders(); + fetch(url, { + method: 'DELETE', + headers: headers, + }); + + return true; + + } catch (error) { + console.log("Error details: ", error); + + if (error instanceof Error) { + return error.message || 'Erreur serveur inconnue lors de la requête.'; + } + + return `Une erreur inattendue s'est produite.`; + } + } + + + + // User Routes /** @@ -328,7 +400,7 @@ public async login(email: string, password: string): Promise { const result: AxiosResponse = await axios.post(url, body, { headers: headers }); if (result.status !== 200) { - throw new Error(`La supression du compte a échoué. Status: ${result.status}`); + throw new Error(`La suppression du compte a échoué. Status: ${result.status}`); } return true; @@ -384,6 +456,7 @@ public async login(email: string, password: string): Promise { } } + /** * @returns folder array if successful * @returns A error string if unsuccessful, @@ -410,7 +483,8 @@ public async login(email: string, password: string): Promise { if (axios.isAxiosError(error)) { const err = error as AxiosError; const data = err.response?.data as { error: string } | undefined; - return data?.error || 'Erreur serveur inconnue lors de la requête.'; + const url = err.config?.url || 'URL inconnue'; + return data?.error || `Erreur serveur inconnue lors de la requête (${url}).`; } return `Une erreur inattendue s'est produite.` diff --git a/client/src/services/WebsocketService.tsx b/client/src/services/WebsocketService.tsx index c5f74be..f19bce5 100644 --- a/client/src/services/WebsocketService.tsx +++ b/client/src/services/WebsocketService.tsx @@ -1,5 +1,6 @@ // WebSocketService.tsx import { io, Socket } from 'socket.io-client'; +import apiService from './ApiService'; // Must (manually) sync these types to server/socket/socket.js @@ -21,10 +22,14 @@ class WebSocketService { private socket: Socket | null = null; connect(backendUrl: string): Socket { - // console.log(backendUrl); - this.socket = io(`${backendUrl}`, { + this.socket = io( '/',{ + path: backendUrl, transports: ['websocket'], - reconnectionAttempts: 1 + autoConnect: true, + reconnection: true, + reconnectionAttempts: 10, + reconnectionDelay: 10000, + timeout: 20000, }); return this.socket; } @@ -37,9 +42,9 @@ class WebSocketService { } } - createRoom() { + createRoom(roomName: string) { if (this.socket) { - this.socket.emit('create-room'); + this.socket.emit('create-room', roomName || undefined); } } @@ -58,6 +63,8 @@ class WebSocketService { endQuiz(roomName: string) { if (this.socket) { this.socket.emit('end-quiz', { roomName }); + //Delete room in mongoDb, roomContainer will be deleted in cleanup + apiService.deleteRoom(roomName); } } diff --git a/create-branch-image.bat b/create-branch-image.bat new file mode 100644 index 0000000..02e6a21 --- /dev/null +++ b/create-branch-image.bat @@ -0,0 +1,74 @@ +@echo off +setlocal EnableDelayedExpansion + +:: Check if gh is installed +where gh >nul 2>&1 +if %errorlevel% neq 0 ( + echo GitHub CLI not found. Installing... + winget install --id GitHub.cli + if %errorlevel% neq 0 ( + echo Failed to install GitHub CLI. Exiting... + exit /b 1 + ) + echo GitHub CLI installed successfully. +) + +:: Check if user is authenticated +gh auth status >nul 2>&1 +if %errorlevel% neq 0 ( + echo GitHub CLI not authenticated. Please authenticate... + gh auth login + if %errorlevel% neq 0 ( + echo Failed to authenticate. Exiting... + exit /b 1 + ) + echo Authentication successful. +) + +:: Get the current branch name +for /f "tokens=*" %%i in ('git rev-parse --abbrev-ref HEAD') do set BRANCH_NAME=%%i + +:: Run the GitHub workflow with the current branch name +echo Running GitHub workflow with branch %BRANCH_NAME%... +gh workflow run 119194149 --ref %BRANCH_NAME% + +:: Wait and validate workflow launch +set /a attempts=0 +set /a max_attempts=12 +echo Waiting for workflow to start... + +:wait_for_workflow +timeout /t 15 >nul +set /a attempts+=1 + +:: Get recent workflow run matching our criteria with in_progress status +for /f "tokens=*" %%i in ('gh run list --branch %BRANCH_NAME% --status in_progress --limit 1 --json databaseId --jq ".[0].databaseId"') do set WORKFLOW_RUN_ID=%%i + +if "%WORKFLOW_RUN_ID%"=="" ( + if !attempts! lss !max_attempts! ( + echo Attempt !attempts! of !max_attempts!: No running workflow found yet... + goto wait_for_workflow + ) else ( + echo Timeout waiting for workflow to start running. + exit /b 1 + ) +) + +echo Found running workflow ID: %WORKFLOW_RUN_ID% + +:monitor_progress +cls +echo Workflow Progress: +echo ---------------- +gh run view %WORKFLOW_RUN_ID% --json jobs --jq ".jobs[] | \"Job: \" + .name + \" - Status: \" + .status + if .conclusion != null then \" (\" + .conclusion + \")\" else \"\" end" +echo. + +:: Check if workflow is still running +for /f "tokens=*" %%i in ('gh run view %WORKFLOW_RUN_ID% --json status --jq ".status"') do set CURRENT_STATUS=%%i +if "%CURRENT_STATUS%" == "completed" ( + echo Workflow completed. + exit /b 0 +) + +timeout /t 5 >nul +goto monitor_progress \ No newline at end of file diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml new file mode 100644 index 0000000..3c02939 --- /dev/null +++ b/docker-compose.local.yaml @@ -0,0 +1,137 @@ +version: '3' + +services: + + frontend: + container_name: frontend + build: + context: ./client + dockerfile: Dockerfile + ports: + - "5173:5173" + networks: + - quiz_network + restart: always + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:$${PORT} || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + + backend: + build: + context: ./server + dockerfile: Dockerfile + container_name: backend + networks: + - quiz_network + ports: + - "3000:3000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + 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 + depends_on: + mongo: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:$${PORT}/health || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + + quizroom: # Forces image to update + build: + context: ./quizRoom + dockerfile: Dockerfile + container_name: quizroom + ports: + - "4500:4500" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - quiz_network + restart: always + healthcheck: + test: ["CMD", "/usr/src/app/healthcheck.sh"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + + nginx: + build: + context: ./nginx + dockerfile: Dockerfile + container_name: nginx + ports: + - "80:80" + depends_on: + frontend: + condition: service_healthy + backend: + condition: service_healthy + networks: + - quiz_network + restart: always + #environment: + # - PORT=8000 + # - FRONTEND_HOST=frontend + # - FRONTEND_PORT=5173 + # - BACKEND_HOST=backend + # - BACKEND_PORT=3000 + healthcheck: + test: ["CMD-SHELL", "wget --spider http://0.0.0.0:$${PORT}/health || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + + mongo: + image: mongo + container_name: mongo + ports: + - "27017:27017" + tty: true + volumes: + - mongodb_data:/data/db + networks: + - quiz_network + restart: always + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 20s + + 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 + networks: + - quiz_network + restart: always + +networks: + quiz_network: + driver: bridge + +volumes: + mongodb_data: + external: false diff --git a/docker-compose.yaml b/docker-compose.yaml index 770f6db..bb5a2ac 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -33,8 +33,21 @@ services: AUTHENTICATED_ROOMS: false volumes: - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json + - /var/run/docker.sock:/var/run/docker.sock depends_on: - mongo + - keycloak + 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 @@ -72,6 +85,23 @@ services: - WATCHTOWER_INCLUDE_RESTARTING=true - WATCHTOWER_SCHEDULE=0 0 5 * * * # At 5 am everyday restart: always + + keycloak: + container_name: keycloak + image: quay.io/keycloak/keycloak:latest + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin123 + KC_HEALTH_ENABLED: 'true' + KC_FEATURES: preview + ports: + - "8080:8080" + volumes: + - ./oauth-tester/config.json:/opt/keycloak/data/import/realm-config.json + command: + - start-dev + - --import-realm + - --hostname-strict=false volumes: mongodb_data: diff --git a/documentation/.gitignore b/documentation/.gitignore new file mode 100644 index 0000000..d97e100 --- /dev/null +++ b/documentation/.gitignore @@ -0,0 +1 @@ +site \ No newline at end of file diff --git a/documentation/deploy.py b/documentation/deploy.py new file mode 100644 index 0000000..abdc3c4 --- /dev/null +++ b/documentation/deploy.py @@ -0,0 +1,2 @@ +from ghp_import import ghp_import +ghp_import('site', push=True, force=True) \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/api.md b/documentation/docs/developpeur/backend/api.md new file mode 100644 index 0000000..faa9ab4 --- /dev/null +++ b/documentation/docs/developpeur/backend/api.md @@ -0,0 +1,12 @@ +## À Propos + +Ce projet utilise Node.js Express pour créer un backend simple pour l'application. + +## Routes API + +Vous pouvez consulter toutes les routes utilisables du backend ici + +* User : https://documenter.getpostman.com/view/32663805/2sA2rCU28v#e942a4f4-321c-465b-bf88-e6c1f1d6f6c8 +* Quiz : https://documenter.getpostman.com/view/32663805/2sA2rCU28v#732d980b-02fd-4807-b5bc-72725098b9b0 +* Folders : https://documenter.getpostman.com/view/32663805/2sA2rCU28v#49ecd432-ccfc-4c8a-8390-b3962f0d5fd7 +* Images : https://documenter.getpostman.com/view/32663805/2sA2rCU28v#58382180-d6f0-492d-80c3-e09de1c368b8 \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/auth.md b/documentation/docs/developpeur/backend/auth.md new file mode 100644 index 0000000..736202c --- /dev/null +++ b/documentation/docs/developpeur/backend/auth.md @@ -0,0 +1,384 @@ +# Authentification + +## Introduction + +Le but du module d'authentification est de pouvoir facilement faire des blocs de code permettant une authentification +personalisée. Il est possible de le faire grâce à cette architecture. Pour la première version de cette fonctionalité, +l'introduction de OIDC et de OAuth sont priorisé ainsi que la migration du module d'authentification simple. + + +## Déconstruction simple de la structure +La structure est la suivante : + +Le AuthManager s'occupe de centraliser les requêtes d'authentification. Ce dernier initialise les autres modules et est +la source de vérité dans les aspects liés à l'authentification. Les modules sont automatiquement chargés par +l'utilisation de variables d'environment. + +Le module s'occupe de créer les routes nécessaires pour son fonctionnement et de créer les utilisateurs. Ces modules +vont appeller le AuthManager afin de confirmer leurs actions avec le login/register de celui-ci. + +Dans le cas de modules plus complexe, tels que le module Passport, la chaine peut être prolongée afin de maintenir +les actions centralisée . Chaque connecteur de PassportJs est initialisé par le module de PassportJs. + + +## Besoins exprimés + + +Modularité et généricité : + +- Le système d'authentification doit être adaptable à diverses configurations, notamment pour répondre aux exigences +spécifiques des différentes universités ou institutions. + +Utilisation de différentes méthodes d'authentification : + +- L'application doit permettre de gérer plusieurs fournisseurs d'authentification (SSO, LDAP, OAuth, etc.) de manière +centralisée et flexible. + +Facilité de configuration : + +- Le système doit permettre une configuration simple et flexible, adaptée à différents environnements (développement, +production, etc.). + +Gestion des permissions : + +- Il doit être possible de définir et de mapper facilement les permissions et les rôles des utilisateurs pour sécuriser +l’accès aux différentes fonctionnalités de l’application. + +Maintien de la connexion : + +- Le système doit garantir la persistance de la connexion pendant toute la durée de l'utilisation de l'application +(exemple : quiz), avec la possibilité de se reconnecter sans perte de données en cas de déconnexion temporaire. + +## Recits utilisateurs pris en comptes + +- En tant qu'utilisateur de projet FOSS, je veux que le module d'authentification soit modulaire et générique afin de +l'adapter à mes besoins. +- En tant qu'administrateur, je veux que les droits des utilisateurs soient inférés par l'authentificateur de l'établissement. +- En tant qu'administrateur, je veux que la configuration des authentificateurs soit simple +- En tant qu'administrateur, je veux configurer les connexions à partir de variables d'environnement ou fichier de config. +- En tant qu'utilisateur, je veux que ma connexion soit stable. +- En tant qu'utilisateur, je veux pouvoir me reconnecter à une salle s'il survient un problème de connexion. + +## Diagrammes + +### Structure +```plantuml +@startuml + +package Backend { +class AuthManager{ + +IAuthModule[] auths + #userInfos + + -load() + -registerAuths() + +showAuths() + + +authStatus() + +logIn(UserInfos) + +register(UserInfos) + +logOut() +} + +interface IAuthModule{ + +registerAuth() + +authenticate() + +register() + +showAuth() +} + +class SimpleFormAuthModule{ + +} + +class PassportAuthModule{ + IPassportProviderDefinition[] providers +} + +Interface IPassportProviderDefinition{ + +name + +type +} + +class OAuthPassportProvider{ + +clientId + +clientSecret + +configUrl + +authorizeUrl + +tokenUrl + +userinfoUrl + +logoutUrl + +JWKSUrl +} + +IAuthModule <|-- SimpleFormAuthModule +IAuthModule <|-- PassportAuthModule +IPassportProviderDefinition <|-- OAuthPassportProvider + +AuthManager -> IAuthModule +PassportAuthModule -> IPassportProviderDefinition +} + +package Frontend{ + class AuthDrawer{ + +IAuthVisual[] getAuthsVisual() + +drawAuths() + } + + Interface IAuthVisual{ + +draw() + } + + class FormVisual{ + +FormInput[] formInputs + } + + interface FormInput{ + +name + +label + +type + +value + } + + AuthDrawer -> IAuthVisual + IAuthVisual <|-- FormVisual + FormVisual -> FormInput +} + +@enduml +``` + + +### Explication des communications : Passport Js +```plantuml +@startuml + +box "Frontend" +participant User +Participant App +end box + +box "Backend" +participant PassportAuthModule +participant Db +participant AuthManager +end box + +box "Auth Server" +participant AuthServer +end box + +User -> App : Get auth page +App -> User : auth page + +User -> App : click OAuth button +App -> User : redirect to OAuth + +User -> AuthServer: Login +AuthServer -> User: Redirect to Auth endpoint with token + +User -> PassportAuthModule: Authenticate with token + +PassportAuthModule -> AuthServer: get user info +AuthServer -> PassportAuthModule: userInfo + +alt login + PassportAuthModule -> Db : fetch local userInfo + Db->PassportAuthModule: userInfo + PassportAuthModule -> PassportAuthModule: Merge userInfo definition + PassportAuthModule -> Db : update user profile + Db->PassportAuthModule: userInfo +end + +alt register + PassportAuthModule -> Db : fetch local userInfo + Db->PassportAuthModule: null + PassportAuthModule -> Db : create user profile + Db->PassportAuthModule: userInfo +end + +PassportAuthModule -> AuthManager : login(userInfos) + +AuthManager -> User: Give refresh token + Redirect to page +User -> App: get / +App -> User: Show Authenticated / +@enduml +``` + +### Explication des communications : SimpleAuth +```plantuml +@startuml + +box "Frontend" +participant User +Participant App +end box + +box "Backend" +participant SimpleAuthModule +participant Db +participant AuthManager +end box + +User -> App : Get auth page +App -> User : auth page + + +alt Login + User -> App : Send Login/Pass + + App -> SimpleAuthModule: Send login/pass + + SimpleAuthModule -> Db: get user info + Db->SimpleAuthModule: user info + SimpleAuthModule -> SimpleAuthModule: Validate Hash +end + +alt register + User -> App : Send Username + Password + Email + + App -> SimpleAuthModule: Send Username + Password + Email + + SimpleAuthModule -> Db: get user info + Db -> SimpleAuthModule : null + + SimpleAuthModule -> Db: put user info +end + +SimpleAuthModule -> AuthManager: userInfo +AuthManager -> User: Give refresh token + Redirect to page +User -> App: get / +App -> User: Show Authenticated / +@enduml +``` + +### Comment les boutons sont affichés +```plantuml +@startuml + +box "FrontEnd" +participant User +Participant FrontEnd +Participant AuthDrawer +end box + +box "BackEnd" +participant API +participant AuthManager +participant Db +participant IAuthModule +end box + +API -> API : load global configurations + +create AuthManager +API -> AuthManager : instanciate with auth configurations + + +create IAuthModule +AuthManager -> IAuthModule : instanciate array + +loop For each auth in auths + AuthManager -> IAuthModule : register + IAuthModule -> API : register routes + API -> IAuthModule : route registration confirmation + IAuthModule -> AuthManager : module registration confirmation +end + +User -> FrontEnd : get login page + +alt already logged in + FrontEnd -> User: redirected to authenticated page +end + +FrontEnd -> AuthDrawer : get auth visual +AuthDrawer -> API : get auth form data + +API -> AuthManager : get auth form data + + +loop For each auth in auths + AuthManager -> IAuthModule : get form data + IAuthModule -> AuthManager : form data +end + +AuthManager -> API : auth fom data +API -> AuthDrawer : auth form data + +AuthDrawer -> AuthDrawer : make auth html +AuthDrawer -> FrontEnd : auth HTML +FrontEnd -> User : show auth page + + +@enduml +``` + +### Comment les sessions sont conservées +```plantuml +@startuml + box "Frontend" + participant User + Participant App + end box + + box "Backend" + participant AuthManager + participant IAuthModules + end box + + App -> AuthManager : send refresh token + + AuthManager -> IAuthModules: ForEach check if logged + IAuthModules -> AuthManager: is authenticated ? + + alt one logged in + AuthManager -> App : send new token + end + + alt all logged out + AuthManager -> App : send error + App -> App : destroy token + App -> User : redirect to login page + end + +@enduml +``` + +## Configuration des variables d'environnement + +Example de configuration du fichier : `server/auth_config.json` : + +```json +{ + "auth": { + "passportjs": // Module + [ + { + "gmatte": { // Nom du sous-module Passport + "type": "oauth", // type + "OAUTH_AUTHORIZATION_URL": "https://auth.gmatte.xyz/application/o/authorize/", + "OAUTH_TOKEN_URL": "https://auth.gmatte.xyz/application/o/token/", + "OAUTH_USERINFO_URL": "https://auth.gmatte.xyz/application/o/userinfo/", + "OAUTH_CLIENT_ID": "--redacted--", + "OAUTH_CLIENT_SECRET": "--Redacted--", + "OAUTH_ADD_SCOPE": "groups", // scopes supplémentaire nécessaire pour le pivot + "OAUTH_ROLE_TEACHER_VALUE": "groups_evaluetonsavoir-prof", // valeur de pivot afin de définir un enseignant + "OAUTH_ROLE_STUDENT_VALUE": "groups_evaluetonsavoir" // valeur de pivot afin de définir un étudiant + } + }, + { + "etsmtl":{ + "type":"oidc", + "OIDC_CONFIG_URL":"https://login.microsoftonline.com/70aae3b7-9f3b-484d-8f95-49e8fbb783c0/v2.0/.well-known/openid-configuration", + "OIDC_CLIENT_ID": "--redacted--", + "OIDC_CLIENT_SECRET": "--redacted--", + "OIDC_ADD_SCOPE": "", + "OIDC_ROLE_TEACHER_VALUE": "groups_evaluetonsavoir-prof", + "OIDC_ROLE_STUDENT_VALUE": "groups_evaluetonsavoir" + } + } + ], + "simpleauth":{} + } +} +``` \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/base-de-donnees.md b/documentation/docs/developpeur/backend/base-de-donnees.md new file mode 100644 index 0000000..68821ee --- /dev/null +++ b/documentation/docs/developpeur/backend/base-de-donnees.md @@ -0,0 +1,11 @@ +# Type de base de données +La base de données est une MongoDB. + +# Collections disponibles +* Files : Ceci est la collection qui contient les différents quiz et leurs questions. +* Folders : Ceci est la collection qui contient les dossiers qui servent à la gestion des différents quiz +* Images : C'est dans cette collection que sont stockées les images utilisées dans les quiz +* Users : Cette collection est utilisée pour la gestion des utilisateurs + +# Information sur la création +Lors du démarrage du projet, la base de données est créée automatiquement. diff --git a/documentation/docs/developpeur/backend/katex.md b/documentation/docs/developpeur/backend/katex.md new file mode 100644 index 0000000..d1deac1 --- /dev/null +++ b/documentation/docs/developpeur/backend/katex.md @@ -0,0 +1,43 @@ +# KaTeX + +KaTeX est le module qui s'occupe de formater les formules mathématiques selon la configuration donnée. + +Les formules entourées de $$ s'afficheront centrées sur leur propre ligne + +`.replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))` + +alors que les formules entourées de $ s'afficheront sur la même ligne + +`.replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false }))` + +La configuration du formatage peut être trouvée dans le fichier TextType.ts situé dans le dossier +EvalueTonSavoir/client/src/components/GiftTemplate/templates + +C'est aussi dans ce fichier que le format markdown est pris en charge. + +## Éditeur de quiz +Pour l'affichage dans l'éditeur de quiz, on peut retrouver la classe TextType être appliquée sur différents éléments +du dossier templates, par exemple la classe Numerical.ts. + +On peut voir ici que le TextType est appliqué sur le contenu de la question: +```typescript +

${TextType({text: stem })}

+``` + +Selon ce qui avait été écrit dans la question, la classe s'occupera de formatter les bonnes sections. + +## Affichage de questions + +Le module React-latex était utilisé pour le formatage des questions durant un quiz, mais cela a apporté un problème +de disparité d'affichage entre la création et l'affichage des questions avec des formules mathématiques. +Les classes affichant les questions durant un quiz peuvent utiliser ce format, mais avec une manipulation de plus. + +Les variables contenant la question doivent d'abord avoir un type TextFormat pour pouvoir faire appel à la classe qui +s'occupe du format sous le module KaTeX. Puis, étant sur un environnement React, il faut utiliser la propriété +dangerouslySetInnerHTML pour afficher la question correctement. + + +`
+ ` + +Ce type de manipulation peut être utilisé dans d'autres environnements React si on veut éviter d'utiliser React-latex. \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/quiz.md b/documentation/docs/developpeur/backend/quiz.md new file mode 100644 index 0000000..ce3c666 --- /dev/null +++ b/documentation/docs/developpeur/backend/quiz.md @@ -0,0 +1,54 @@ +# Example de Quiz + +```gift +//-----------------------------------------// +// Examples from gift/format.php. +//-----------------------------------------// + +Who's buried in Grant's tomb?{~Grant ~Jefferson =no one} + +Grant is {~buried =entombed ~living} in Grant's tomb. + +Grant is buried in Grant's tomb.{FALSE} + +Who's buried in Grant's tomb?{=no one =nobody} + +When was Ulysses S. Grant born?{#1822:5} + +Match the following countries with their corresponding capitals. { + =Canada -> Ottawa + =Italy -> Rome + =Japan -> Tokyo + =India -> New Delhi + ####It's good to know the capitals +} + +//-----------------------------------------// +// More complicated examples. +//-----------------------------------------// + +::Grant's Tomb::Grant is { + ~buried#No one is buried there. + =entombed#Right answer! + ~living#We hope not! +} in Grant's tomb. + +Difficult multiple choice question.{ + ~wrong answer #comment on wrong answer + ~%50%half credit answer #comment on answer + =full credit answer #well done!} + +::Jesus' hometown (Short answer ex.):: Jesus Christ was from { + =Nazareth#Yes! That's right! + =%75%Nazereth#Right, but misspelled. + =%25%Bethlehem#He was born here, but not raised here. +}. + +//this comment will be ignored by the filter +::Numerical example:: +When was Ulysses S. Grant born? {# + =1822:0 #Correct! 100% credit + =%50%1822:2 #He was born in 1822. + You get 50% credit for being close. +} +``` \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/salle-de-quiz-swagger.json b/documentation/docs/developpeur/backend/salle-de-quiz-swagger.json new file mode 100644 index 0000000..2084a39 --- /dev/null +++ b/documentation/docs/developpeur/backend/salle-de-quiz-swagger.json @@ -0,0 +1,146 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Room API" + }, + "servers":[ + { + "url": "http://localhost", + "description": "Via Docker" + }, + { + "url": "http://localhost:3000", + "description": "Via npm" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/api/room": { + "get": { + "summary": "Get all rooms", + "description": "Returns a list of rooms", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + }, + "post": { + "summary": "Create a new room", + "description": "Creates a new room, returns the created room", + "responses": { + "200": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + }, + "/api/room/{roomId}": { + "get": { + "summary": "Get a room by id", + "description": "Returns a room by id", + "parameters": [ + { + "name": "roomId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a room by id", + "description": "Deletes a room by id", + "parameters": [ + { + "name": "roomId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "Room": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": "autoincrement" + }, + "name": { + "type": "string" + }, + "host": { + "type": "string" + }, + "nbStudents": { + "type": "integer", + "default": 0 + }, + "mustBeCleaned": { + "type": "boolean", + "default": false + } + }, + "required": [ + "id", + "name", + "host" + ] + } + } + } + } \ No newline at end of file diff --git a/documentation/docs/developpeur/backend/salle-de-quiz.md b/documentation/docs/developpeur/backend/salle-de-quiz.md new file mode 100644 index 0000000..08d76c7 --- /dev/null +++ b/documentation/docs/developpeur/backend/salle-de-quiz.md @@ -0,0 +1,193 @@ +# Salles de Quiz + +## Introduction + +Les salles de quiz ont été extraites dans leur propre conteneur afin de limiter les dégâts liés soit à une +surutilisation d'une salle soit à une attaque sur le logiciel. + +En effet, le découplement permet a un quiz de: + + - Survivre même si le backend est non-fonctionnel + - Mourir sans entrainer toute l'application avec elle + - Créer/Supprimer des salles automatiquement dépendant de la demande + +Pour effectuer ceci, il faut faire une petite gymnastique. Il y a une route dans l'api servant à gérer les salles. +Lorsqu'un utilisateur demande le socket d'une salle : "/api/rooms/{id}/socket", la requête rebondit sur le proxy Nginx. +Celui-ci contacte le backend afin d'obtenir l'adresse de l'ordinateur auquel envoyer la requête et redirige le socket +vers cette adresse. + +## Déconstruction simple de la structure + +Un module supplémentaire a été ajouté à la structure : Rooms. + +L'objet `room` est la définition d'une salle de façon minimaliste. Cette définition est aggrandie avec l'information +récoltée du "provider". +Le `provider` est le système gérant les différentes salles. Dans l'implémentation effectuée, il s'agit de docker. + +Lorsque l'api des salles est instantié, celui-ci est lié avec un "provider", définissant comment les salles seront créées. +L'api des salles permet de les ajouter, les supprimer, et les consulter. + +L'api lance deux "jobs": + +- Une vérification de l'état de santé des salles. Celle-ci roule tous les 10 secondes et met a jour les salles. +- Une suppression des salles. Celle-ci roule tous les 30 secondes et supprimme automatiquement les salles ayant la +mention de suppression. + +## Besoins exprimés + +Fiabilite : + +- Nous voulons s'assurer qu'il soit possible d'avoir un grand nombre d'élèves présent sans qu'il y ait des problèmes de +déconnexions +- Nous voulons que le temps de réponse soit faible +- Nous voulons que le système soit capable de fonctionner de facon indépendante + +## Recis utilisateurs pris en comptes + +- En tant qu'enseignant, je veux que tout mes élèves soient capable de se connecter à la salle de classe rapidement +- En tant qu'enseignant, je veux que la salle de quiz puisse survivre des pannes liées aux autres modules de l'aplication +- En tant qu'administrateur, je veux que les salles soient indépendantes et n'impactent pas les performances des autres salles +- En tant qu'administrateur, je veux que les salles puissent être hébergées séparément du projet + +## Diagrammes + +### Structure +```plantuml +@startuml +class Room{ + +id + +name + +host + +nbStudents + +mustBeCleaned +} + +class RoomRepository { + +get(id) + +create(room) + +delete(id) + +update(room,id) + +getAll() +} + +class RoomController { + +setupRoom(options) + +deleteRoom(roomId) + +listRooms() + +getRoomStatus(roomId) + +updateRoom(room,roomId) +} + +class RoomRouter{ + + / : GET + + /:id : GET + + / : POST + + /:id : PUT + + /:id : DELETE +} + +class BaseRoomProvider { + +createRoom(roomid,options) + +deleteRoom(roomId) + +getRoomInfo(roomId) + +getRoomStatus(roomId) + +listRooms() + -cleanup() + -syncInstantiatedRooms() + #updateRoomInfos() +} + +class DockerRoomProvider +circle Dockerode + + +Room - RoomRepository +BaseRoomProvider o-- RoomRepository +DockerRoomProvider --|> BaseRoomProvider +DockerRoomProvider -left- Dockerode +Dockerode o-- QuizRoom +RoomController o-- BaseRoomProvider +RoomRouter o-- RoomController + +class QuizRoom{ + +/health: GET + +create-room() + +join-room() + +next-question() + +launch-student-mode() + +end-quiz() + +submit-answers() + -disconnect() +} +@enduml +``` +Remarque: Les signatures de fonctions semblent un peu partout car il y a des fonctions de classes standard, des appels +HTTPS et des appels de sockets dans le même diagramme. + +### Diagramme de séquence démontrant les communications +```plantuml +@startuml + actor Teacher + actor Student + entity Nginx + entity Frontend + entity Api + entity Docker + entity Database + +group Quiz Creation + Teacher -> Frontend : Create a quizroom + Frontend -> Api : Create a quizroom + Api -> Docker : Create a quizroom + Docker -> QuizRoom ** + QuizRoom -> Docker : creation successful + Docker -> Api : Creation Successful + + loop every seconds until healthy or 30s: + Api -> QuizRoom : Checking Health via /health + QuizRoom -> Api : Doesn't answer, answer healthy or unhealthy + end + + Api -> Database : Create Room + Database -> Api : Room created + Api -> Teacher : Route to room socket +end + +group Quiz Joining: + Teacher -> Nginx : Join Room + Nginx -> Api : Get room infos from id + Api -> Nginx : Ip:port of room + Nginx -> QuizRoom: Give teacher's connexion + + Student -> Frontend: Join Room X + Frontend -> Nginx : Join Room X + Nginx -> Api : Get room infos from id + Api -> Nginx : Ip:port of room + Nginx -> QuizRoom: Give student's connexion + + QuizRoom -> QuizRoom : Give Quiz ... (Multiple actions) + + Student -> QuizRoom: Disconnect + Teacher -> QuizRoom: Disconect +end + +group QuizManagement (Every 10 seconds) + Api -> QuizRoom : Checking number of people in the room + QuizRoom -> Api : Number of people (0) or Unhealthy + Api -> Database : Mark room to deletion +end + +group Quiz Deletion (Every 30 seconds) + Api -> Database : Give all rooms marked for deletion + Database -> Api : rooms + Api -> Docker : delete rooms + Docker -> QuizRoom : delete + Docker -> Api : Deleted +end + +@enduml +``` + +## API + + diff --git a/documentation/docs/developpeur/deploiements/ansible.md b/documentation/docs/developpeur/deploiements/ansible.md new file mode 100644 index 0000000..ada79d4 --- /dev/null +++ b/documentation/docs/developpeur/deploiements/ansible.md @@ -0,0 +1,77 @@ +# Documentation de déploiement avec Ansible + +Ce guide explique comment utiliser **Ansible** pour déployer facilement le projet **ÉvalueTonSavoir**. + +## Prérequis + +### Système requis +- Un ordinateur sous **Linux** ou **Mac**. +- Pour **Windows**, installez [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) afin d'exécuter un environnement Ubuntu. + +### Installation d'Ansible +1. **Sur Ubuntu (ou WSL2)** : + Utilisez le gestionnaire de paquets `apt` : + ```bash + sudo apt update + sudo apt install ansible-core + ``` +2. **Autres systèmes** : + Consultez la [documentation officielle d'Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) pour connaître les étapes spécifiques à votre système. + +### Installation de Docker et Docker Compose +- Suivez la [documentation Docker officielle](https://docs.docker.com/get-docker/) pour installer Docker. +- Docker Compose est inclus comme plugin Docker dans les versions récentes. + +## Téléchargement des fichiers nécessaires + +1. Clonez le dépôt Git contenant les fichiers Ansible : + ```bash + git clone https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir + ``` +2. Naviguez vers le répertoire `ansible` : + ```bash + cd EvalueTonSavoir/ansible + ``` + +## Déploiement avec Ansible + +### Commande de déploiement +Pour déployer l'application, exécutez la commande suivante dans le répertoire contenant le fichier `deploy.yml` : +```bash +ansible-playbook -i inventory.ini deploy.yml +``` + +### Structure des fichiers utilisés +- **`inventory.ini`** : Défini les cibles du déploiement. Par défaut, il est configuré pour un déploiement local. +- **`deploy.yml`** : Playbook contenant les instructions pour installer, configurer et déployer l'application. + +### Étapes effectuées par Ansible +1. **Installation des dépendances** : + - Vérifie et installe Docker si nécessaire. +2. **Démarrage des services** : + - Télécharge le fichier `docker-compose.yaml` depuis le dépôt Github. + - Lance les services définis avec Docker Compose. +3. **Vérification des conteneurs** : + - Vérifie que les conteneurs sont en cours d'exécution et fonctionnent correctement. + +## Vérification du déploiement + +Une fois le playbook exécuté, Ansible : +1. Installe Docker et ses dépendances. +2. Télécharge et configure le projet. +3. Lance les services avec Docker Compose. +4. Vérifie que les services sont accessibles localement. + +Pour tester l'application, utilisez la commande suivante : +```bash +curl http://localhost:8080 +``` +Un code de réponse `200 OK` indiquera que le déploiement est réussi. + +--- + +## Résumé + +Le déploiement avec **Ansible** simplifie la gestion des configurations et l'installation des dépendances nécessaires +pour le projet **ÉvalueTonSavoir**. Avec cette méthode, vous pouvez déployer rapidement l'application dans un +environnement local tout en assurant une configuration cohérente. diff --git a/documentation/docs/developpeur/deploiements/local.md b/documentation/docs/developpeur/deploiements/local.md new file mode 100644 index 0000000..239b95d --- /dev/null +++ b/documentation/docs/developpeur/deploiements/local.md @@ -0,0 +1,63 @@ +## Prérequis + +- Assurez-vous d'avoir Node JS installé en téléchargeant la dernière version depuis [https://nodejs.org/en](https://nodejs.org/en). + +- Ensuite, assurez-vous d'avoir accès à un serveur MongoDB de développement + +> Pour plus d'informations sur la base de données, veuillez consulter la documentation [[ici|Base-de-données]] + +- Cloner le projet avec la commande suivante : + ``` + git clone https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git + ``` + +## Étape 1 - Démarrage du backend + +1. Naviguez vers le répertoire du projet en utilisant la commande suivante : + ``` + cd .\EvalueTonSavoir\server\ + ``` + +2. Assurez-vous de créer le fichier .env et d'y ajouter les paramètres appropriés. Vous pouvez vous inspirer du fichier +.env.example pour connaître les paramètres nécessaires. + + [[Voir ici la documentation des configurations|Configurations]] + +3. Installez les dépendances avec la commande suivante : + ``` + npm install + ``` + +4. Démarrez le serveur en utilisant la commande suivante : + ``` + npm run dev + ``` + +5. Ouvrez votre navigateur et accédez à l'URL indiquée dans la console (par exemple, http://localhost:4400). + +## Étape 2 - Démarrage du frontend + +1. Naviguez vers le répertoire du projet en utilisant la commande suivante : + ``` + cd .\EvalueTonSavoir\client\ + ``` +> [!WARNING] +> Assurez-vous que le backend est en cours d'exécution avant de démarrer le frontend. \ +> Notez également l'URL du serveur pour le fichier `.env`. + +2. Assurez-vous de créer le fichier .env et d'y ajouter les paramètres appropriés. Vous pouvez vous inspirer du fichier +.env.example pour connaître les paramètres nécessaires. + + [[Voir ici la documentation des configurations|Configurations]] + +3. Installez les dépendances avec la commande suivante : + ``` + npm install + ``` + +4. Démarrez le frontend avec la commande suivante : + ``` + npm run dev + ``` + +5. Ouvrez votre navigateur et accédez à l'URL indiquée dans la console (par exemple, http://localhost:5173/). diff --git a/documentation/docs/developpeur/deploiements/opentofu.md b/documentation/docs/developpeur/deploiements/opentofu.md new file mode 100644 index 0000000..ed4f4b2 --- /dev/null +++ b/documentation/docs/developpeur/deploiements/opentofu.md @@ -0,0 +1,61 @@ +# Documentation de déploiement avec OpenTofu + +Ce guide explique comment **OpenTofu** est utilisé pour déployer facilement le projet **ÉvalueTonSavoir**. + +## Déploiement + +### Étapes à réaliser pour faire le déploiement + +Pour déployer à l'aide de OpenTofu, il suffit de suivre les étapes du fichier [README.md](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/blob/main/opentofu/README.md). + +### Structure des fichiers utilisés pour le déploiement sur Azure +- **`app.tf`** : Défini les configurations de la machine virtuelle qui exécute l'application. +- **`database.tf`** : Défini les configurations de la base de données. +- **`main.tf`** : Défini le fournisseur utilisé pour le déploiement, dans ce cas-ci Azure. +- **`network.tf`** : Défini les configurations réseau et les règles de sécurité réseau. +- **`resource_group.tf`** : Défini les configurations du groupes de ressources dans Azure. +- **`storage.tf`** : Défini les configurations pour stocker et pouvoir utiliser le fichier auth_config.json. +- **`terraform.tfvars`** : Défini les valeurs des variables à utiliser lors du déploiement. +- **`variables.tf`** : Défini toutes les variables qui sont utilisées lors du déploiement. + +### Étapes effectuées par OpenTofu +1. **Création des éléments du réseau** : + - Création d'un réseau virtuel. + - Création d'un sous-réseau. + - Création d'une adresse ip publique. + - Création d'un groupe de sécurité réseau. + - Création d'une interface réseau. +2. **Création de la base de données** : + - Création du serveur de base de données. + - Création de la base de données (collection puisqu'on utilise MongoDB) +3. **Création de la machine virtuelle** : + - Création de la machine virtuelle. + - Installation de Docker + - Récupération du fichier `docker-compose.yaml` depuis le dépôt Github. + - Exécution de l'application avec le fichier `docker-compose.yaml` + +## Résumé + +Le déploiement avec **OpenTofu** simplifie la gestion des éléments nécessaires pour déployer le projet +**ÉvalueTonSavoir**. dans l'infonuagique. Avec cette méthode, vous pouvez déployer rapidement et facilement +l'application dans un environnement infonuagique. + +## Diagramme de sequence + +```plantuml +@startuml + +actor Administrator +participant "Control Machine" as control_machine +participant "Azure" as azure + +Administrator -> control_machine: "Se connecte à Azure" +Administrator -> control_machine: "Lancer le déploiement avec OpenTofu" +control_machine -> azure: "Crée les éléments réseaux" +control_machine -> azure: "Crée la base de données" +control_machine -> azure: "Crée la machine virtuelle qui exécute l'application" +control_machine <- azure: "OpenTofu retourne le résultat (success/échec)" +Administrator <- control_machine: "OpenTofu retourne le résultat (success/échec)" + +@enduml +``` \ No newline at end of file diff --git a/documentation/docs/developpeur/deploiements/prod.md b/documentation/docs/developpeur/deploiements/prod.md new file mode 100644 index 0000000..478ad89 --- /dev/null +++ b/documentation/docs/developpeur/deploiements/prod.md @@ -0,0 +1,230 @@ + +## Introduction + +Nous avons choisi d'exécuter les composantes de cette application avec Docker, car cela simplifie le processus de +gestion des processus d'application. + +Voici un diagramme de déploiement expliquant la relation des composantes et comment les images Docker sont créées et +déployées dans un serveur. + +```plantuml +@startuml +skinparam style strictuml +skinparam component { + BackgroundColor<> LightBlue + BackgroundColor<> lightgreen +} +node "evalsa.etsmtl.ca" { + artifact "docker-compose.yml" as compose + node "Docker" as docker { + [evaluetonsavoir-routeur\n(nginx)] <> as ROC + [evaluetonsavoir-frontend\n(vite + TypeScript React)] <> as FEC + component "evaluetonsavoir-backend\n(Express, Javascript)" <> as BEC { + port API_REST + port SOCKET_SALLE + } + } + database "MongoDB" as BD + BD -- BEC + +} + +node "Docker hub" { + component evaluetonsavoir-routeur <> as RO { + } + component evaluetonsavoir-frontend <> as FE { + } + component evaluetonsavoir-backend <> as BE { + } +} + +node "GitHub" { + artifact "routeur-deploy.yml" <> as RO_D + artifact "backend-deploy.yml" <> as BE_D + artifact "frontend-deploy.yml" <> as FE_D +} + +BE <-- BE_D : on commit +FE <-- FE_D +RO <-- RO_D + +BEC <.. BE : "pull à 5h du matin" +FEC <.. FE +ROC <.. RO + +node "Navigateur moderne\n(Windows/Android)" as browser { + [React App] as RA_NAV +} + +RA_NAV <.. FEC : chargée à partir des pages web +RA_NAV ..> API_REST : API REST +RA_NAV <..> SOCKET_SALLE : WebSocket +@enduml +``` + +## Prérequis + +Les STI nous a fourni un serveur avec les spécifications suivantes : + +- Ubuntu 22.04 LTS +- CPU : 4 cœurs +- RAM : 8 Go +- HDD : 100 Go +- Certificat SSL + +Les STI ont déjà effectué la configuration initiale de la machine selon leurs normes de mise en place d'un serveur pour +assurer la bonne maintenance et sécurité au sein de leur infrastructure. Cette configuration inclut un utilisateur non root. + +Vous aurez également besoin d'un compte Docker Hub, ou vous pouvez simplement créer une PR sur le projet principal et +elle sera déployée automatiquement. + +## Étape 1 - Installation de Docker + +Connectez-vous avec les informations d'identification de l'ETS : +``` +ssh @ +``` + +Tout d'abord, mettez à jour votre liste existante de packages : +``` +sudo apt update +``` + +Ensuite, installez quelques packages prérequis qui permettent à apt d'utiliser des packages via HTTPS : +> [!WARNING] +> Si vous voyez l'erreur suivante, ARRÊTEZ. Contactez les STI pour résoudre le problème. \ +> `Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 10703 (apt)` +``` +sudo apt install apt-transport-https ca-certificates curl software-properties-common +``` + +Ajoutez la clé GPG du référentiel Docker officiel à votre système : +``` +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +``` + +Ajoutez le référentiel Docker aux sources APT : +``` +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +``` + +Mettez à jour à nouveau votre liste existante de packages pour que l'ajout soit reconnu : +``` +sudo apt update +``` + +Assurez-vous que vous vous apprêtez à installer à partir du référentiel Docker plutôt que du référentiel Ubuntu par défaut : +``` +apt-cache policy docker-ce +``` + +Vous verrez une sortie comme celle-ci, bien que le numéro de version pour Docker puisse être différent : +```Output +docker-ce: + Installed: (none) + Candidate: 5:26.0.0-1~ubuntu.22.04~jammy + Version table: + 5:26.0.0-1~ubuntu.22.04~jammy 500 + 500 https://download.docker.com/linux/ubuntu jammy/stable amd64 Packages + 5:25.0.5-1~ubuntu.22.04~jammy 500 + 500 https://download.docker.com/linux/ubuntu jammy/stable amd64 Packages +... +``` + +Installez Docker : +``` +sudo apt install docker-ce +``` + +Vérifiez que Docker fonctionne : +``` +sudo systemctl status docker +``` + +La sortie devrait être similaire à ce qui suit, montrant que le service est actif et en cours d'exécution : +```Output +● docker.service - Docker Application Container Engine + Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2024-04-05 13:20:12 EDT; 1min 24s ago +TriggeredBy: ● docker.socket + Docs: https://docs.docker.com + Main PID: 19389 (dockerd) + Tasks: 10 + Memory: 28.7M + CPU: 172ms + CGroup: /system.slice/docker.service + └─19389 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock +... +``` + +> [!NOTE] +> Si Docker ne roule pas, p.ex. vous voyez : +> ``` +> ○ docker.service - Docker Application Container Engine +> Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) +> Active: inactive (dead) +> ``` +> Vous devez démarrer Docker : +> ``` +> sudo systemctl start docker +> ``` + +## Étape 2 - Installation de Docker Compose + +Créez un répertoire d'installation Docker Compose : +``` +mkdir -p ~/.docker/cli-plugins/ +``` + +Obtenez Docker Compose : +``` +curl -SL https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose +``` + +Ensuite, définissez les permissions correctes pour que la commande docker compose soit exécutable : +``` +chmod +x ~/.docker/cli-plugins/docker-compose +``` + +Pour vérifier que l'installation a réussi, vous pouvez exécuter : +``` +docker compose version +``` + + +## Étape 3 - Ajouter notre projet + + +Commencez par créer un nouveau répertoire dans votre dossier personnel : +``` +mkdir ~/EvalueTonSavoir +``` + +Puis déplacez-vous dans le répertoire : +``` +cd ~/EvalueTonSavoir +``` + +Créez un fichier `docker-compose.yaml` à partir du dépôt GitHub : +``` +curl -SL https://raw.githubusercontent.com/ets-cfuhrman-pfe/EvalueTonSavoir/main/docker-compose.yaml -o docker-compose.yaml +``` + +> [!NOTE] +> Avant de continuer, veuillez noter qu'il est crucial de mettre à jour les variables d'environnement dans le script, +> car les valeurs actuelles sont des modèles génériques. Assurez-vous de personnaliser ces variables selon les besoins +> spécifiques de votre environnement avant d'exécuter le script. + +Avec le fichier docker-compose.yml en place, vous pouvez maintenant exécuter Docker Compose pour démarrer votre environnement : +``` +sudo docker compose up -d +``` + +Vérifiez que les services fonctionne : +``` +sudo docker ps -a +``` + +## Conclusion + +Félicitations ! Vous avez maintenant avec succès configuré et lancé EvalueTonSavoir sur votre serveur, prêt à être utilisé. diff --git a/documentation/docs/developpeur/documentation/a-propos.md b/documentation/docs/developpeur/documentation/a-propos.md new file mode 100644 index 0000000..5d9183a --- /dev/null +++ b/documentation/docs/developpeur/documentation/a-propos.md @@ -0,0 +1,28 @@ +# A propos + +## Lancer la documentation +Pour lancer la documentation, il faut installer python et entrer dans le dossier documentation. +Il faut ensuite installer les dépendances avec `pip install -r requirements.txt`. +Pour lancer le mode développement il faut executer `python -m mkdocs serve` +Afin d'accélérer le déploiement et ne pas être touché par des erreurs de "rate-limiting", il est préférable d'utiliser +une image docker de plantuml. Pour cela, il faut utiliser la commande suivante : +`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat` + +## Déploiement +Le code est automatiquement déployé par la github-action `create-docs.yaml` +Celle-ci ouvre le repo et fait les mêmes étapes que "lancer la documentation". +Il y a une différence, elle utilise `build` au lieu de `serve` pour ensuite publier avec l'outil [`ghp-import`](https://github.com/c-w/ghp-import). +La page est poussée sur la branche [`gh-pages`](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/tree/gh-pages) et ensuite publié en tant que [gh-page](https://pages.github.com/) + +## Themes et Plugins +Si vous ajoutez des plugins, veuillez mettre a jour le fichier `requirements.txt`. + +La documentation utilise [MkDocs](https://www.mkdocs.org/) avec [le theme matérial]((https://squidfunk.github.io/mkdocs-material/)). Il y a bien des fonctionalités tel que les c +ode-blocks qui peuvent être activés. +Vous pouvez avoir accès a la documentation ici : [https://squidfunk.github.io/mkdocs-material/reference/code-blocks/](https://squidfunk.github.io/mkdocs-material/reference/code-blocks/) + +## Autre méthode de lancement (virtuel) +Si vous avez un probleme avec votre environement et vous avez besoin d'un environement virtuel, il s'agit de faire +`python -m venv .venv` dans le dossier document et d'activer cet environemment avec le fichier activate (changeant +dépendant de votre invite de commande) : `.venv\script\activate`. +Vous pouvez ensuite continuer les autres étapes. \ No newline at end of file diff --git a/documentation/docs/developpeur/frontend/index.md b/documentation/docs/developpeur/frontend/index.md new file mode 100644 index 0000000..1ab89b5 --- /dev/null +++ b/documentation/docs/developpeur/frontend/index.md @@ -0,0 +1,22 @@ +## À Propos + +Ce projet représente une interface utilisateur React pour notre application. + +## GIFT text format render (code source) + +Le code original a été développé pour créer une extension VS afin de prendre en charge le format de texte GIFT. + +Le code peut être trouvé ici: [https://codesandbox.io/s/gift-templates-iny09](https://codesandbox.io/s/gift-templates-iny09) + +Nous avons décidé de réutiliser ce code car il fournit un aperçu proche de ce à quoi ressemblent les quiz dans Moodle, +étant une plateforme bien connue à l'École de Technologie Supérieure (ÉTS). + +Pour réutiliser le code, nous avons dû installer les packages NPM suivants: + +- [katex](https://www.npmjs.com/package/katex) : Une bibliothèque JavaScript rapide et facile à utiliser pour le rendu mathématique TeX sur le web. +- [marked](https://www.npmjs.com/package/marked) : Un analyseur syntaxique et un compilateur de markdown construit pour la vitesse. +- [nanoid](https://www.npmjs.com/package/nanoid) : Un générateur d'identifiants de chaîne unique, sécurisé, convivial pour les URL et minuscule (108 octets) pour JavaScript. +- [gift-pegjs](https://www.npmjs.com/package/gift-pegjs) : Un analyseur GIFT pour JavaScript utilisant PEG.js. +- [@types/katex](https://www.npmjs.com/package/@types/katex) : Définitions TypeScript pour katex. +- [@types/marked](https://www.npmjs.com/package/@types/marked) : Définitions TypeScript pour marked. +- [@types/nanoid](https://www.npmjs.com/package/@types/nanoid) : Définitions TypeScript pour nanoid. diff --git a/documentation/docs/developpeur/index.md b/documentation/docs/developpeur/index.md new file mode 100644 index 0000000..f11bb04 --- /dev/null +++ b/documentation/docs/developpeur/index.md @@ -0,0 +1,83 @@ +# Structure haut niveau + +## But du projet +ÉvalueTonSavoir a été créé dû aux coûts importants des versions entreprises des logiciels similaires tels que Socrative et +Kahoot. Le but principal est d’être capable d’avoir une plateforme auto-hébergée et bien intégrée dans les systèmes +déjà présents des établissements scolaire. + +## Requis + +Le but du projet est d'avoir un outil gratuit et libre afin d'améliorer l'apprentissage avec les fonctionnalités suivantes : + +- Permettre aux personnel enseignant de créer des quizs +- Permettre aux enseignant de collecter les résultats des quizs +- Permettre aux étudiants de faire ces quizs +- Permettre aux étudiants d'avoir une rétroaction + +Afin de limiter le niveau de difficulté d'intégration du personnel enseignant: + +- L'utilisation du format [`GIFT`](https://docs.moodle.org/405/en/GIFT_format) déja présent dans moodle doit être utilisé +- Le personnel et les étudiants doivent être capable de s'authentifier avec le portail de l'école +- Le démarrage du quiz doit se faire de façon rapide et efficace. + +Afin de faciliter le déploiement de masse : + +- Le logiciel doit être facile a déployer sur des machines locales +- Le logiciel doit être facile a déployer sur le cloud +- Le logiciel doit s'interconnecter à l'infrastructure présente +- Le logiciel doit être performant et fiable + +## Architecture actuelle + +```plantuml +@startuml + +package Proxy{ + component Nginx +} + +package App{ + component Frontend + component Backend + database MongoDb +} + +cloud Provider{ + component QuizRoom +} + + +Nginx --down-> Backend +Nginx --down-> Frontend +Nginx --down-> Provider + +Backend --right-> MongoDb +Backend --up-> Nginx + +Frontend --up-> Nginx + +@enduml +``` + +### Details techniques + +Le tableau ci-dessus est simplifié grandement car toutes les composantes sont individuelles. Ce qui veut dire que chacune +des parties pouraient être déployées sur un serveur différent et tout de même fonctionner. Ceci permettrai de distribuer +la charge de travail facilement entre plusieurs serveurs. + +Le proxy Nginx permet de camoufler la séparation du backend et frontend en réunissant les deux parties sous la même url. +Il a aussi la tâche de diriger les appels de sockets vers leur machine interne dans le provider. + +Le frontend dessert la partie visuelle de l'application. + +Le backend s'occupe de tout les services suivants : + +- La gestion des utilisateurs +- La gestion des quizs +- La gestion des médias +- La gestion des salles + + +### Liens vers détails supplémentaires +- [Gestion de l'authentification](./backend/auth.md) +- [Gestion de la salle de Quiz](./backend/salle-de-quiz.md) \ No newline at end of file diff --git a/documentation/docs/developpeur/test/test-charge-output.png b/documentation/docs/developpeur/test/test-charge-output.png new file mode 100644 index 0000000000000000000000000000000000000000..e335abc48e07072764e2c9d004231aee1f95b58a GIT binary patch literal 53828 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 a>YVENUH1jY2Ce5HzezPx literal 0 HcmV?d00001 diff --git a/documentation/docs/developpeur/test/test-de-charge.md b/documentation/docs/developpeur/test/test-de-charge.md new file mode 100644 index 0000000..434bc9e --- /dev/null +++ b/documentation/docs/developpeur/test/test-de-charge.md @@ -0,0 +1,79 @@ +# Tests de Charge + +Pour tester la montée en charge et les performances du projet, un **test de charge** est inclus dans `test/stressTest`. Il est conçu spécifiquement pour **evalue-ton-savoir**, avec un focus sur les communications serveur-client et client-client. + +--- + +## Routes utilisé sur le quizRoom +- **`get-usage`** : Récupère les ressources des conteneurs du réseau. +- **`message-from-teacher`** : Transfert de messages des professeurs aux étudiants. +- **`message-from-student`** : Transfert de messages des étudiants aux professeurs. + +--- + +## Fonctionnement + +1. **Authentification** : Récupère un token depuis l’API backend. +2. **Configuration** : Crée les salles de quiz et connecte un professeur à chaque salle. +3. **Connexion des étudiants** : Connecte les étudiants aux salles selon les paramètres. +4. **Simulation** : Messages simulés entre professeurs et étudiants. +5. **Collecte des données** : Collecte les métriques de ressources pour analyse. + +--- + +## Exécution + +L'exécution des commandes doit se faire ici: `/test/stressTest` + +### Directe + +```bash +node main.js +``` +- Node.js doit être installé. +- Modifiez les variables dans main.js. + +### Docker + +```bash +docker-compose up +``` +- Docker doit être installé. +- Configurez un fichier .env. + + +## Variables d’Environnement + +Les variables sont définies dans un fichier `.env` : + +- **BASE_URL** : URL à tester. +- **USER_EMAIL**, **USER_PASSWORD** : Identifiants pour créer et gérer les salles. +- **NUMBER_ROOMS** : Nombre de salles. +- **USERS_PER_ROOM** : Nombre d’étudiants par salle. + +### Variables Optionnelles +- **MAX_MESSAGES_ROUND** : Nombre maximum de messages par cycle. +- **CONVERSATION_INTERVAL** : Délai (ms) entre les messages. +- **MESSAGE_RESPONSE_TIMEOUT** : Délai (ms) avant de considérer un message sans réponse. +- **BATCH_DELAY** : Délai (ms) entre les envois par lots. +- **BATCH_SIZE** : Taille des lots de messages. + +--- + +## Résultats Collectés + +### Métriques +- **Salles créées / échouées** +- **Utilisateurs connectés / échoués** +- **Messages tentés, envoyés, reçus** + +### Rapports +- **JSON** : Pour analyse automatisée. +- **Rapport texte** : Résumé lisible. +- **Graphiques** *(via ChartJS)* : + - **CPU**, **mémoire**, **charge**. + + +### Exemple graphique: + +![ Exemple graphique](./test-charge-output.png) \ No newline at end of file diff --git a/documentation/docs/index.md b/documentation/docs/index.md new file mode 100644 index 0000000..c9cd0e2 --- /dev/null +++ b/documentation/docs/index.md @@ -0,0 +1,23 @@ +# A propos + +EvalueTonSavoir est une plateforme open source et auto-hébergée qui poursuit le développement du code provenant de [https://github.com/ETS-PFE004-Plateforme-sondage-minitest](https://github.com/ETS-PFE004-Plateforme-sondage-minitest). Cette plateforme minimaliste est conçue comme un outil d'apprentissage et d'enseignement, offrant une solution simple et efficace pour la création de quiz utilisant le format GIFT, similaire à Moodle. + +## Fonctionnalités clés + +* Open Source et Auto-hébergé : Possédez et contrôlez vos données en déployant la plateforme sur votre propre infrastructure. +* Compatibilité GIFT : Créez des quiz facilement en utilisant le format GIFT, permettant une intégration transparente avec d'autres systèmes d'apprentissage. +* Minimaliste et Efficace : Une approche bare bones pour garantir la simplicité et la facilité d'utilisation, mettant l'accent sur l'essentiel de l'apprentissage. + +## Contribution + +Actuellement, il n'y a pas de modèle établi pour les contributions. Si vous constatez quelque chose de manquant ou si vous pensez qu'une amélioration est possible, n'hésitez pas à ouvrir un issue et/ou une PR) + +## Liens utiles + +* [Dépôt d'origine Frontend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) +* [Dépôt d'origine Backend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) +* [Documentation (Wiki)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki) + +## License + +EvalueTonSavoir is open-sourced and licensed under the [MIT License](/LICENSE). \ No newline at end of file diff --git a/documentation/docs/javascripts/katex.js b/documentation/docs/javascripts/katex.js new file mode 100644 index 0000000..3828300 --- /dev/null +++ b/documentation/docs/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ], + }) + }) \ No newline at end of file diff --git a/documentation/docs/utilisateur/configuration.md b/documentation/docs/utilisateur/configuration.md new file mode 100644 index 0000000..9280a1b --- /dev/null +++ b/documentation/docs/utilisateur/configuration.md @@ -0,0 +1,84 @@ +> [!NOTE] +> Chaque projet contient un fichier `.env.example` fournissant des exemples de configuration. +> Assurez-vous de consulter ce fichier pour vous inspirer des paramètres nécessaires à votre configuration. + +> [!NOTE] +> Ce sont toutes les options de configuration. N'hésitez pas à ouvrir une PR si vous en voyez qui manquent. + +## Options de Configuration Backend + +| Variable d'Environnement | Description | Exemple | Optionnel | +|---|---|---|---| +| `PORT` | Le port sur lequel l'application fonctionne | 4400 | non| +| `MONGO_URI` | La chaîne de connexion pour se connecter à la base de données mongodb | `mongodb://localhost:27017` or `mongodb://127.0.0.1:27017` (the former can cause trouble on Windows depending on hosts files) | non| +| `MONGO_DATABASE` | Le nom souhaité pour la base de données | evaluetonsavoir | non| +| `EMAIL_SERVICE` | Le service utilisé pour les e-mails | gmail | non| +| `SENDER_EMAIL` | L'adresse e-mail utilisée pour l'envoi | monadresse@gmail.com | non| +| `EMAIL_PSW` | Le mot de passe de l'adresse e-mail | 'monmotdepasse' | non| +| `JWT_SECRET` | Le secret utilisé pour la gestion des JWT | monsecretJWT | non| +| `FRONTEND_URL` | URL du frontend, y compris le port | http://localhost:5173 | non| + +## Options de Configuration Frontend + +| Variable d'Environnement | Description | Exemple | Optionnel | +|---|---|---|---| +| `VITE_BACKEND_URL` | URL du backend, y compris le port | http://localhost:4400 | non| +| `VITE_AZURE_BACKEND_URL` | URL du backend, y compris le port | http://localhost:4400 | non| + +## Options de Configuration du routeur +| Variable d'Environnement | Description | Exemple | Optionnel défaut | +|---|---|---|---| +| `PORT` | Numero de port sur lequel la NGINX écoute | http://localhost:80 | oui| +| `FRONTEND_HOST` | Url relié au Frontend | http://localhost |oui +| `FRONTEND_PORT` | Port relié au Frontend | http://localhost:5173 | oui| +| `BACKEND_HOST` | Url relié au Backend | http://localhost |oui +| `BACKEND_PORT` | Port relié au Backend | http://localhost:3000 | oui| + +## Options de Configuration de la salle de Quiz +| Variable d'Environnement | Description | Exemple | Optionnel défaut | +|---|---|---|---| +| `PORT` | Numero de port sur lequel la salle écoute | http://localhost:4500 | oui| +| `ROOM_ID` | Numéro de la salle | http://localhost/rooms/000000 | oui| + +## HealthChecks + +### Frontend + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:$${PORT} || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + +### Backend + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:$${PORT}/health || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + +### Salle de Quiz + healthcheck: + test: ["CMD", "/usr/src/app/healthcheck.sh"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + +### Routeur + healthcheck: + test: ["CMD-SHELL", "wget --spider http://0.0.0.0:$${PORT}/health || exit 1"] + interval: 5s + timeout: 10s + start_period: 5s + retries: 6 + +### MongoDb + + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 20s \ No newline at end of file diff --git a/documentation/docs/utilisateur/deploiment.md b/documentation/docs/utilisateur/deploiment.md new file mode 100644 index 0000000..48a975d --- /dev/null +++ b/documentation/docs/utilisateur/deploiment.md @@ -0,0 +1,18 @@ +# Déploiement + +Les méthodes recommandées de déploiement sont via Ansible et Opentofu. +Ansible est utilisés afin de faire un déploiement sur un serveur local, opentofu sur le cloud. + +## Ansible + +Le déploiement avec ansible est un déploiement simplifié. +Il vous suffit d'avoir un ordinateur linux/mac ou pouvant faire exécuter [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) +dans le cas de windows. Il faut ensuite utiliser le gestionnaire de paquet (souvent apt) afin d'installer +le paquet `ansible-core`, d'autres méthodes sont indiquées dans la [documentation officielle de ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html). +Une fois le tout fait, vous pouvez telécharger [les fichiers nécéssaire](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/ansible) et lancer la commande +`ansible-playbook -i inventory.ini deploy.yml` + +## OpenTofu +Le déploiement avec OpenTofu est un peu plus complexe mais il permet d'héberger la solution sur votre cloud préféré. +Il suffit [d'installer OpenTofu](https://opentofu.org/docs/intro/install/) et de téléchgarger [les fichiers nécéssaires](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/opentofu). +Un Readme est inclus afin d'organiser votre grappe de serveurs. \ No newline at end of file diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml new file mode 100644 index 0000000..66b5850 --- /dev/null +++ b/documentation/mkdocs.yml @@ -0,0 +1,79 @@ +site_name: EvalueTonSavoir +repo_url: https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir +edit_uri: edit/main/documentation/docs + +theme: + language: fr + icon: + repo: fontawesome/brands/github + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: red + accent: pink + toggle: + icon: material/brightness-7 + name: Mode sombre + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: red + accent: pink + toggle: + icon: material/brightness-4 + name: Mode clair + features: + - content.code.copy + - content.code.select + - content.code.annotate + - navigation.instant + - navigation.instant.progress + - navigation.tracking + - content.action.edit + highlightjs: true + hljs_languages: + - javascript + - typescript + - css + - react + - yaml + - latex + - katex + - gift + +use_directory_urls: false + +plugins: + - search + - offline + - plantuml: + puml_url: !ENV [PUMLURL,'http://localhost:8080'] # dev + puml_keyword: plantuml + theme: + light: material/red-light + dark: material/red-dark + - swagger-ui-tag: + docExpansion: "list" + tryItOutEnabled: false + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.arithmatex: + generic: true + +extra_javascript: + - javascripts/katex.js + - https://unpkg.com/katex@0/dist/katex.min.js + - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js + +extra_css: + - https://unpkg.com/katex@0/dist/katex.min.css \ No newline at end of file diff --git a/documentation/python-version b/documentation/python-version new file mode 100644 index 0000000..fdcfcfd --- /dev/null +++ b/documentation/python-version @@ -0,0 +1 @@ +3.12 \ No newline at end of file diff --git a/documentation/requirements.txt b/documentation/requirements.txt new file mode 100644 index 0000000..ffb48c2 --- /dev/null +++ b/documentation/requirements.txt @@ -0,0 +1,7 @@ +mkdocs +mkdocs[i18n] +mkdocs_puml +mkdocs-material +Pygments +ghp-import +mkdocs-swagger-ui-tag \ No newline at end of file diff --git a/nginx/.env.example b/nginx/.env.example new file mode 100644 index 0000000..3898f5f --- /dev/null +++ b/nginx/.env.example @@ -0,0 +1,5 @@ +PORT=80 +FRONTEND_HOST=frontend +FRONTEND_PORT=5173 +BACKEND_HOST=backend +BACKEND_PORT=3000 \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 6597d48..16e263f 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,3 +1,90 @@ -FROM nginx +# Stage 1: Build stage +FROM nginx:1.27-alpine AS builder +# Install required packages +RUN apk add --no-cache nginx-mod-http-js nginx-mod-http-keyval -COPY ./default.conf /etc/nginx/conf.d/default.conf \ No newline at end of file +# Stage 2: Final stage +FROM alpine:3.19 + +# Install gettext for envsubst and other dependencies +RUN apk add --no-cache \ + gettext \ + curl \ + nginx-mod-http-js \ + nginx-mod-http-keyval \ + pcre2 \ + ca-certificates \ + pcre \ + libgcc \ + libstdc++ \ + zlib \ + libxml2 \ + libedit \ + geoip \ + libxslt + +# Create base nginx directory +RUN mkdir -p /etc/nginx + +# Copy Nginx and NJS modules from builder +COPY --from=builder /usr/sbin/nginx /usr/sbin/ +COPY --from=builder /usr/lib/nginx/modules/ /usr/lib/nginx/modules/ +RUN rm -rf /etc/nginx/* +COPY --from=builder /etc/nginx/ /etc/nginx/ +COPY --from=builder /usr/lib/nginx/ /usr/lib/nginx/ + +# Setup directories and permissions +RUN mkdir -p /var/cache/nginx \ + && mkdir -p /var/log/nginx \ + && mkdir -p /etc/nginx/conf.d \ + && mkdir -p /etc/nginx/njs \ + && mkdir -p /etc/nginx/templates \ + && chown -R nginx:nginx /var/cache/nginx \ + && chown -R nginx:nginx /var/log/nginx \ + && chown -R nginx:nginx /etc/nginx \ + && touch /var/run/nginx.pid \ + && chown nginx:nginx /var/run/nginx.pid \ + && chmod 777 /var/log/nginx + +# Copy necessary libraries from builder +COPY --from=builder /usr/lib/libxml2.so* /usr/lib/ +COPY --from=builder /usr/lib/libexslt.so* /usr/lib/ +COPY --from=builder /usr/lib/libgd.so* /usr/lib/ +COPY --from=builder /usr/lib/libxslt.so* /usr/lib/ + +# Modify nginx.conf to load modules +RUN echo 'load_module modules/ngx_http_js_module.so;' > /tmp/nginx.conf && \ + cat /etc/nginx/nginx.conf >> /tmp/nginx.conf && \ + mv /tmp/nginx.conf /etc/nginx/nginx.conf + +# Copy configurations +COPY templates/default.conf /etc/nginx/templates/ +COPY njs/main.js /etc/nginx/njs/ +COPY entrypoint.sh /entrypoint.sh +RUN dos2unix /entrypoint.sh + +ENV PORT=80 \ + FRONTEND_HOST=frontend \ + FRONTEND_PORT=5173 \ + BACKEND_HOST=backend \ + BACKEND_PORT=3000 + +# Set final permissions +RUN chmod +x /entrypoint.sh && \ + chown -R nginx:nginx /etc/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /var/cache/nginx && \ + chmod 755 /etc/nginx && \ + chmod 777 /etc/nginx/conf.d && \ + chmod 644 /etc/nginx/templates/default.conf && \ + chmod 644 /etc/nginx/conf.d/default.conf + +# Switch to nginx user +USER nginx + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget -q --spider http://0.0.0.0:${PORT}/health || exit 1 + +# Start Nginx using entrypoint script +# CMD [ "/bin/sh","-c","sleep 3600" ] # For debugging +ENTRYPOINT [ "/entrypoint.sh" ] \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf deleted file mode 100644 index b698731..0000000 --- a/nginx/default.conf +++ /dev/null @@ -1,31 +0,0 @@ -upstream frontend { - server frontend:5173; -} - -upstream backend { - server backend:3000; -} - -server { - listen 80; - - location /api { - rewrite /backend/(.*) /$1 break; - proxy_pass http://backend; - } - - location /socket.io { - rewrite /backend/(.*) /$1 break; - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_set_header Host $host; - proxy_hide_header 'Access-Control-Allow-Origin'; - } - - location / { - proxy_pass http://frontend; - } - -} diff --git a/nginx/entrypoint.sh b/nginx/entrypoint.sh new file mode 100644 index 0000000..b05cd5d --- /dev/null +++ b/nginx/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# entrypoint.sh + +# We are already running as nginx user +envsubst '${PORT} ${FRONTEND_HOST} ${FRONTEND_PORT} ${BACKEND_HOST} ${BACKEND_PORT}' \ + < /etc/nginx/templates/default.conf \ + > /etc/nginx/conf.d/default.conf + +# Adds logs for docker +ln -sf /dev/stdout /var/log/nginx/access.log +ln -sf /dev/stderr /var/log/nginx/error.log +ln -sf /dev/stderr /var/log/nginx/debug.log + +# Start nginx +exec nginx -g "daemon off;" \ No newline at end of file diff --git a/nginx/njs/main.js b/nginx/njs/main.js new file mode 100644 index 0000000..b5f7b4d --- /dev/null +++ b/nginx/njs/main.js @@ -0,0 +1,97 @@ +function get_cache_dict(r) { + return ''; +} + +function getCachedData(r, key) { + try { + const cached = ngx.shared.cache.get(key); + if (cached) { + const data = JSON.parse(cached); + const now = Date.now(); + // 2 minutes cache - let game rooms rotate + if (now - data.timestamp < 120000) { + r.error(`Debug: Cache hit for ${key}, age: ${(now - data.timestamp)/1000}s`); + return data.value; + } + r.error(`Debug: Cache expired for ${key}, age: ${(now - data.timestamp)/1000}s`); + } + return null; + } catch (error) { + r.error(`Cache read error: ${error}`); + return null; + } +} + +function setCachedData(r, key, value) { + try { + const data = { + timestamp: Date.now(), + value: value + }; + ngx.shared.cache.set(key, JSON.stringify(data)); + r.error(`Debug: Cached ${key}`); + } catch (error) { + r.error(`Cache write error: ${error}`); + } +} + +async function fetchRoomInfo(r) { + const cacheKey = `room:${r.variables.room_id}`; + + try { + const cachedRoom = getCachedData(r, cacheKey); + if (cachedRoom) { + r.error(`Debug: Room info from cache: ${JSON.stringify(cachedRoom)}`); + return cachedRoom; + } + + let res = await r.subrequest('/api/room/' + r.variables.room_id, { + method: 'GET' + }); + + if (res.status !== 200) { + r.error(`Failed to fetch room info: ${res.status}`); + return null; + } + + let room = JSON.parse(res.responseText); + setCachedData(r, cacheKey, room); + r.error(`Debug: Room info fetched and cached: ${JSON.stringify(room)}`); + return room; + + } catch (error) { + r.error(`Error fetching/caching room info: ${error}`); + return null; + } +} + +export default { + get_cache_dict, + routeWebSocket: async function(r) { + try { + const roomInfo = await fetchRoomInfo(r); + + if (!roomInfo || !roomInfo.host) { + r.error(`Debug: Invalid room info: ${JSON.stringify(roomInfo)}`); + r.return(404, 'Room not found or invalid'); + return; + } + + let proxyUrl = roomInfo.host; + if (!proxyUrl.startsWith('http://') && !proxyUrl.startsWith('https://')) { + proxyUrl = 'http://' + proxyUrl; + } + + r.error(`Debug: Original URL: ${r.uri}`); + r.error(`Debug: Setting proxy target to: ${proxyUrl}`); + r.error(`Debug: Headers: ${JSON.stringify(r.headersIn)}`); + + r.variables.proxy_target = proxyUrl; + r.internalRedirect('@websocket_proxy'); + + } catch (error) { + r.error(`WebSocket routing error: ${error}`); + r.return(500, 'Internal routing error'); + } + } +}; \ No newline at end of file diff --git a/nginx/templates/default.conf b/nginx/templates/default.conf new file mode 100644 index 0000000..7fdff8d --- /dev/null +++ b/nginx/templates/default.conf @@ -0,0 +1,81 @@ +js_shared_dict_zone zone=cache:10m; +js_import njs/main.js; +js_set $cache_dict main.get_cache_dict; + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream frontend { + server ${FRONTEND_HOST}:${FRONTEND_PORT}; +} + +upstream backend { + server ${BACKEND_HOST}:${BACKEND_PORT}; +} + +server { + listen ${PORT}; + + set $proxy_target ""; + + location /health { + access_log off; + add_header Content-Type text/plain; + return 200 'healthy'; + } + + location /backend-health { + proxy_pass http://backend/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + access_log off; + } + + location /frontend-health { + proxy_pass http://frontend; + proxy_http_version 1.1; + proxy_set_header Host $host; + access_log off; + } + + location /api { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Game WebSocket routing + location ~/api/room/([^/]+)/socket { + set $room_id $1; + js_content main.routeWebSocket; + } + + # WebSocket proxy location + location @websocket_proxy { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Timeouts + proxy_connect_timeout 7m; + proxy_send_timeout 7m; + proxy_read_timeout 7m; + proxy_buffering off; + + proxy_pass $proxy_target; + } + + location / { + proxy_pass http://frontend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/opentofu/README.md b/opentofu/README.md new file mode 100644 index 0000000..b7de6e5 --- /dev/null +++ b/opentofu/README.md @@ -0,0 +1,44 @@ +# Déploiement avec Opentofu + +## Microsoft Azure + +### Installer opentofu + +https://opentofu.org/docs/intro/install/ + +### Installer Azure CLI + +https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install + +### Se connecter à Azure et récupérer l'id de l'abonnement Azure + +Pour se connecter à Azure, faites la commande suivante + +`az login` + +Avec cette commande, vous allez sélectionner un abonnement Azure. Copiez l'id de l'abonnement, vous en aurez besoin +dans l'étape suivant. + +### Modifier les configurations + +Créer un fichier **terraform.tfvars** sur la base du fichier **terraform.tfvars.example** dans le répertoire **azure**. +Vous pouvez changer toutes les variables utilisée lors du déploiement dans ce fichier. +Toutes les variables, leur description et leur valeur par défaut sont disponibles dans le fichier **variables.tf**. + +Créer un fichier **auth_config.json** sur la base du fichier **auth_config.json.example** dans le répertoire **opentofu**. + +L'url est défini comme suit: http://..cloudapp.azure.com. +Par défaut, l'url est http://evaluetonsavoir.canadacentral.cloudapp.azure.com/ + +### Lancer le déploiement + +Pour lancer le déploiement, faites les commandes suivantes + +`cd azure` +`az login` +`tofu init` +`tofu apply` + +Ensuite, opentofu va afficher toutes les actions qu'il va effectuer avec les valeurs configurées. +Entrez `yes` pour appliquer ces actions et lancer le déploiement. + diff --git a/opentofu/auth_config.json.example b/opentofu/auth_config.json.example new file mode 100644 index 0000000..6e15147 --- /dev/null +++ b/opentofu/auth_config.json.example @@ -0,0 +1,35 @@ +{ + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + { + provider2: { + type: "oidc", + OIDC_CLIENT_ID: "your_oidc_client_id", + OIDC_CLIENT_SECRET: "your_oidc_client_secret", + OIDC_CONFIG_URL: "https://your-issuer.com", + OIDC_ADD_SCOPE: "groups", + OIDC_ROLE_TEACHER_VALUE: "teacher-claim-value", + OIDC_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + "simpleauth": { + enabled: true, + name: "provider3", + SESSION_SECRET: "your_session_secret", + }, + }, +} \ No newline at end of file diff --git a/opentofu/azure/app.tf b/opentofu/azure/app.tf new file mode 100644 index 0000000..a1dada5 --- /dev/null +++ b/opentofu/azure/app.tf @@ -0,0 +1,67 @@ +# Create Virtual Machine +resource "azurerm_linux_virtual_machine" "vm" { + name = var.vm_name + resource_group_name = azurerm_resource_group.resource_group.name + location = azurerm_resource_group.resource_group.location + size = var.vm_size + admin_username = var.vm_user + admin_password = var.vm_password + disable_password_authentication = false + + network_interface_ids = [azurerm_network_interface.nic.id] + + os_disk { + name = var.vm_os_disk_name + caching = "ReadWrite" + storage_account_type = var.vm_os_disk_type + } + + source_image_reference { + publisher = var.vm_image_publisher + offer = var.vm_image_offer + sku = var.vm_image_plan + version = var.vm_image_version + } + + custom_data = base64encode(<<-EOT + #!/bin/bash + sudo apt-get update -y + sudo apt-get install -y docker.io + sudo apt-get install -y docker-compose + sudo systemctl start docker + sudo systemctl enable docker + + sudo usermod -aG docker ${var.vm_user} + sudo newgrp docker + + su - ${var.vm_user} -c ' + + curl -o auth_config.json \ + "https://${azurerm_storage_account.storage_account.name}.file.core.windows.net/${azurerm_storage_share.backend_storage_share.name}/auth_config.json${data.azurerm_storage_account_sas.storage_access.sas}" + + curl -L -o docker-compose.yaml ${var.docker_compose_url} + + export VITE_BACKEND_URL=http://${var.dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.cloudapp.azure.com + export PORT=${var.backend_port} + export MONGO_URI="${azurerm_cosmosdb_account.cosmosdb_account.primary_mongodb_connection_string}" + export MONGO_DATABASE=${azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection.database_name} + export EMAIL_SERVICE=${var.backend_email_service} + export SENDER_EMAIL=${var.backend_email_sender} + export EMAIL_PSW="${var.backend_email_password}" + export JWT_SECRET=${var.backend_jwt_secret} + export SESSION_Secret=${var.backend_session_secret} + export SITE_URL=http://${var.dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.cloudapp.azure.com + export FRONTEND_PORT=${var.frontend_port} + export USE_PORTS=${var.backend_use_port} + export AUTHENTICATED_ROOMS=${var.backend_use_auth_student} + export QUIZROOM_IMAGE=${var.quizroom_image} + + docker-compose up -d + ' + EOT + ) + + depends_on = [ + azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection, + data.azurerm_storage_account_sas.storage_access] +} diff --git a/opentofu/azure/database.tf b/opentofu/azure/database.tf new file mode 100644 index 0000000..efc9fbf --- /dev/null +++ b/opentofu/azure/database.tf @@ -0,0 +1,43 @@ +resource "azurerm_cosmosdb_account" "cosmosdb_account" { + name = var.cosmosdb_account_name + resource_group_name = azurerm_resource_group.resource_group.name + location = azurerm_resource_group.resource_group.location + offer_type = "Standard" + kind = "MongoDB" + mongo_server_version = "7.0" + + is_virtual_network_filter_enabled = true + + virtual_network_rule { + id = azurerm_subnet.subnet.id + } + + capabilities { + name = "EnableMongo" + } + + consistency_policy { + consistency_level = "Session" + } + + geo_location { + failover_priority = 0 + location = azurerm_resource_group.resource_group.location + } + + depends_on = [azurerm_resource_group.resource_group] +} + +resource "azurerm_cosmosdb_mongo_collection" "cosmosdb_mongo_collection" { + name = var.mongo_database_name + resource_group_name = azurerm_resource_group.resource_group.name + account_name = azurerm_cosmosdb_account.cosmosdb_account.name + database_name = var.mongo_database_name + + index { + keys = ["_id"] + unique = true + } + + depends_on = [azurerm_cosmosdb_account.cosmosdb_account] +} \ No newline at end of file diff --git a/opentofu/azure/main.tf b/opentofu/azure/main.tf new file mode 100644 index 0000000..1d1329d --- /dev/null +++ b/opentofu/azure/main.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + } + required_version = ">= 1.0" +} + +provider "azurerm" { + features {} + subscription_id = var.subscription_id +} \ No newline at end of file diff --git a/opentofu/azure/network.tf b/opentofu/azure/network.tf new file mode 100644 index 0000000..d9db2c8 --- /dev/null +++ b/opentofu/azure/network.tf @@ -0,0 +1,87 @@ +# Create Virtual Network +resource "azurerm_virtual_network" "vnet" { + name = var.vnet_name + location = azurerm_resource_group.resource_group.location + resource_group_name = azurerm_resource_group.resource_group.name + address_space = ["10.0.0.0/16"] +} + +# Create Subnet +resource "azurerm_subnet" "subnet" { + name = var.subnet_name + resource_group_name = azurerm_resource_group.resource_group.name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = ["10.0.1.0/24"] + + service_endpoints = ["Microsoft.AzureCosmosDB"] +} + +# Create Public IP Address +resource "azurerm_public_ip" "public_ip" { + name = var.public_ip_name + location = azurerm_resource_group.resource_group.location + resource_group_name = azurerm_resource_group.resource_group.name + allocation_method = "Static" + domain_name_label = var.dns +} + +resource "azurerm_network_security_group" "nsg" { + name = var.nsg_name + location = azurerm_resource_group.resource_group.location + resource_group_name = azurerm_resource_group.resource_group.name + + security_rule { + name = "SSH" + priority = 1000 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = var.nsg_ssh_ip_range + destination_address_prefix = "*" + } + + security_rule { + name = "HTTP" + priority = 1001 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "80" + source_address_prefix = var.nsg_http_ip_range + destination_address_prefix = "*" + } + + security_rule { + name = "HTTPS" + priority = 1002 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = var.nsg_https_ip_range + destination_address_prefix = "*" + } +} + +# Create Network Interface +resource "azurerm_network_interface" "nic" { + name = var.network_interface_name + location = azurerm_resource_group.resource_group.location + resource_group_name = azurerm_resource_group.resource_group.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.subnet.id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = azurerm_public_ip.public_ip.id + } +} + +resource "azurerm_network_interface_security_group_association" "example" { + network_interface_id = azurerm_network_interface.nic.id + network_security_group_id = azurerm_network_security_group.nsg.id +} diff --git a/opentofu/azure/resource_group.tf b/opentofu/azure/resource_group.tf new file mode 100644 index 0000000..ed83082 --- /dev/null +++ b/opentofu/azure/resource_group.tf @@ -0,0 +1,5 @@ +# Create Resource Group +resource "azurerm_resource_group" "resource_group" { + name = var.resource_group_name + location = var.location +} \ No newline at end of file diff --git a/opentofu/azure/storage.tf b/opentofu/azure/storage.tf new file mode 100644 index 0000000..1eb6db7 --- /dev/null +++ b/opentofu/azure/storage.tf @@ -0,0 +1,74 @@ +resource "azurerm_storage_account" "storage_account" { + name = var.config_volume_storage_account_name + resource_group_name = azurerm_resource_group.resource_group.name + location = azurerm_resource_group.resource_group.location + account_tier = "Standard" + account_replication_type = "LRS" + + depends_on = [azurerm_resource_group.resource_group] +} + +resource "azurerm_storage_share" "backend_storage_share" { + name = var.backend_storage_share_name + storage_account_name = azurerm_storage_account.storage_account.name + quota = 1 + + depends_on = [azurerm_storage_account.storage_account] +} + +resource "null_resource" "upload_file" { + provisioner "local-exec" { + command = < { + try { + if (io.engine?.clientsCount !== undefined) { + res.status(200).json({ + status: 'healthy', + path: `/api/room/${roomId}/socket`, + connections: io.engine.clientsCount, + uptime: process.uptime() + }); + } else { + throw new Error('Socket.io server not initialized'); + } + } catch (error: Error | any) { + res.status(500).json({ + status: 'unhealthy', + error: error.message + }); + } +}); + + +const ioOptions: Partial = { + path: `/api/room/${roomId}/socket`, + cors: { + origin: "*", + methods: ["GET", "POST"], + credentials: true, + }, +}; + +const io = new Server(server, ioOptions); + +// Initialize WebSocket setup +setupWebsocket(io); + +server.listen(port, () => { + console.log(`WebSocket server is running on port ${port}`); +}); diff --git a/quizRoom/docker-compose.yml b/quizRoom/docker-compose.yml new file mode 100644 index 0000000..aa43c9f --- /dev/null +++ b/quizRoom/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + quizroom: + build: + context: . + args: + - PORT=${PORT:-4500} + ports: + - "${PORT:-4500}:${PORT:-4500}" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - PORT=${PORT:-4500} + - ROOM_ID=${ROOM_ID} + healthcheck: + test: curl -f http://localhost:${PORT:-4500}/health || exit 1 + interval: 30s + timeout: 30s + retries: 3 + start_period: 30s \ No newline at end of file diff --git a/quizRoom/healthcheck.sh b/quizRoom/healthcheck.sh new file mode 100644 index 0000000..7b0e27f --- /dev/null +++ b/quizRoom/healthcheck.sh @@ -0,0 +1,2 @@ +#!/bin/bash +curl -f "http://0.0.0.0:${PORT}/health" || exit 1 \ No newline at end of file diff --git a/quizRoom/package-lock.json b/quizRoom/package-lock.json new file mode 100644 index 0000000..105118b --- /dev/null +++ b/quizRoom/package-lock.json @@ -0,0 +1,1595 @@ +{ + "name": "quizroom", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "quizroom", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dockerode": "^4.0.2", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "http": "^0.0.1-security", + "socket.io": "^4.8.1" + }, + "devDependencies": { + "@types/dockerode": "^3.3.32", + "@types/express": "^5.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "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==" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.32", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz", + "integrity": "sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/ssh2": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", + "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "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==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "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" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "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==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.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-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "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==", + "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==", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^5.0.3", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.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==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", + "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==" + }, + "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==", + "optional": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.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==" + }, + "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/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "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==", + "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/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "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==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/quizRoom/package.json b/quizRoom/package.json new file mode 100644 index 0000000..fb9f258 --- /dev/null +++ b/quizRoom/package.json @@ -0,0 +1,27 @@ +{ + "name": "quizroom", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "node dist/app.js", + "build": "tsc", + "dev": "ts-node app.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/dockerode": "^3.3.32", + "@types/express": "^5.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + }, + "dependencies": { + "dockerode": "^4.0.2", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "http": "^0.0.1-security", + "socket.io": "^4.8.1" + } +} diff --git a/quizRoom/socket/.env.example b/quizRoom/socket/.env.example new file mode 100644 index 0000000..1af5be7 --- /dev/null +++ b/quizRoom/socket/.env.example @@ -0,0 +1,2 @@ +ROOM_ID=123456 +PORT=4500 \ No newline at end of file diff --git a/quizRoom/socket/setupWebSocket.ts b/quizRoom/socket/setupWebSocket.ts new file mode 100644 index 0000000..3ed5a24 --- /dev/null +++ b/quizRoom/socket/setupWebSocket.ts @@ -0,0 +1,242 @@ +import { Server, Socket } from "socket.io"; +import Docker from 'dockerode'; +import fs from 'fs'; + +const MAX_USERS_PER_ROOM = 60; +const MAX_TOTAL_CONNECTIONS = 2000; + +export const setupWebsocket = (io: Server): void => { + let totalConnections = 0; + + io.on("connection", (socket: 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) => { + // Ensure sentRoomName is a string before applying toUpperCase() + const roomName = (typeof sentRoomName === "string" && sentRoomName.trim() !== "") + ? sentRoomName.toUpperCase() + : generateRoomName(); + + console.log(`Created room with name: ${roomName}`); + if (!io.sockets.adapter.rooms.get(roomName)) { + socket.join(roomName); + socket.emit("create-success", roomName); + } else { + socket.emit("create-failure"); + } + }); + + socket.on("join-room", ({ enteredRoomName, username }: { enteredRoomName: string; username: string }) => { + if (io.sockets.adapter.rooms.has(enteredRoomName)) { + const clientsInRoom = io.sockets.adapter.rooms.get(enteredRoomName)?.size || 0; + + if (clientsInRoom <= MAX_USERS_PER_ROOM) { + socket.join(enteredRoomName); + socket.to(enteredRoomName).emit("user-joined", { id: socket.id, name: username, answers: [] }); + 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 }: { roomName: string; question: string }) => { + socket.to(roomName).emit("next-question", question); + }); + + socket.on("launch-student-mode", ({ roomName, questions }: { roomName: string; questions: string[] }) => { + socket.to(roomName).emit("launch-student-mode", questions); + }); + + socket.on("end-quiz", ({ roomName }: { roomName: string }) => { + socket.to(roomName).emit("end-quiz"); + }); + + socket.on("message", (data: string) => { + 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, + }: { + roomName: string; + username: string; + answer: string; + idQuestion: string; + }) => { + socket.to(roomName).emit("submit-answer-room", { + idUser: socket.id, + username, + answer, + idQuestion, + }); + }); + + socket.on("error", (error) => { + console.error("WebSocket server error:", error); + }); + + + // Stress Testing + + socket.on("message-from-teacher", ({ roomName, message }: { roomName: string; message: string }) => { + console.log(`Message reçu dans la salle ${roomName} : ${message}`); + socket.to(roomName).emit("message-sent-teacher", { message }); + }); + + socket.on("message-from-student", ({ roomName, message }: { roomName: string; message: string }) => { + console.log(`Message reçu dans la salle ${roomName} : ${message}`); + socket.to(roomName).emit("message-sent-student", { message }); + }); + + interface ContainerStats { + containerId: string; + containerName: string; + memoryUsedMB: number | null; + memoryUsedPercentage: number | null; + cpuUsedPercentage: number | null; + error?: string; + } + + class ContainerMetrics { + private docker: Docker; + private containerName: string; + + private bytesToMB(bytes: number): number { + return Math.round(bytes / (1024 * 1024)); + } + + constructor() { + this.docker = new Docker({ + socketPath: process.platform === 'win32' ? '//./pipe/docker_engine' : '/var/run/docker.sock' + }); + this.containerName = `room_${process.env.ROOM_ID}`; + } + + private async getContainerNetworks(containerId: string): Promise { + const container = this.docker.getContainer(containerId); + const info = await container.inspect(); + return Object.keys(info.NetworkSettings.Networks); + } + + public async getAllContainerStats(): Promise { + try { + // First get our container to find its networks + const ourContainer = await this.docker.listContainers({ + all: true, + filters: { name: [this.containerName] } + }); + + if (!ourContainer.length) { + throw new Error(`Container ${this.containerName} not found`); + } + + const ourNetworks = await this.getContainerNetworks(ourContainer[0].Id); + + // Get all containers + const allContainers = await this.docker.listContainers(); + + // Get stats for containers on the same networks + const containerStats = await Promise.all( + allContainers.map(async (container): Promise => { + try { + const containerNetworks = await this.getContainerNetworks(container.Id); + // Check if container shares any network with our container + if (!containerNetworks.some(network => ourNetworks.includes(network))) { + return null; + } + + const stats = await this.docker.getContainer(container.Id).stats({ stream: false }); + + const memoryStats = { + usage: stats.memory_stats.usage, + limit: stats.memory_stats.limit || 0, + percent: stats.memory_stats.limit ? (stats.memory_stats.usage / stats.memory_stats.limit) * 100 : 0 + }; + + const cpuDelta = stats.cpu_stats?.cpu_usage?.total_usage - (stats.precpu_stats?.cpu_usage?.total_usage || 0); + const systemDelta = stats.cpu_stats?.system_cpu_usage - (stats.precpu_stats?.system_cpu_usage || 0); + const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * (stats.cpu_stats?.online_cpus || 1) * 100 : 0; + + return { + containerId: container.Id, + containerName: container.Names[0].replace(/^\//, ''), + memoryUsedMB: this.bytesToMB(memoryStats.usage), + memoryUsedPercentage: memoryStats.percent, + cpuUsedPercentage: cpuPercent + }; + } catch (error) { + return { + containerId: container.Id, + containerName: container.Names[0].replace(/^\//, ''), + memoryUsedMB: null, + memoryUsedPercentage: null, + cpuUsedPercentage: null, + error: error instanceof Error ? error.message : String(error) + }; + } + }) + ); + + // Change the filter to use proper type predicate + return containerStats.filter((stats): stats is ContainerStats => stats !== null); + } catch (error) { + console.error('Stats error:', error); + return [{ + containerId: 'unknown', + containerName: 'unknown', + memoryUsedMB: null, + memoryUsedPercentage: null, + cpuUsedPercentage: null, + error: error instanceof Error ? error.message : String(error) + }]; + } + } + } + + const containerMetrics = new ContainerMetrics(); + + socket.on("get-usage", async () => { + try { + const usageData = await containerMetrics.getAllContainerStats(); + socket.emit("usage-data", usageData); + } catch (error) { + socket.emit("error", { message: "Failed to retrieve usage data" }); + } + }); + + }); + + const generateRoomName = (length = 6): string => { + const characters = "0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; + }; +}; diff --git a/quizRoom/tsconfig.json b/quizRoom/tsconfig.json new file mode 100644 index 0000000..7847690 --- /dev/null +++ b/quizRoom/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["./**/*"], + "exclude": ["node_modules"] + } + \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile index 02bb17e..8dbd6ff 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -8,6 +8,10 @@ RUN npm install COPY ./ . -EXPOSE 4400 +ENV PORT=3000 +EXPOSE ${PORT} + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:${PORT}/health || exit 1 CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/server/__mocks__/AppError.js b/server/__mocks__/AppError.js new file mode 100644 index 0000000..0073aa4 --- /dev/null +++ b/server/__mocks__/AppError.js @@ -0,0 +1,8 @@ +class AppError extends Error { + constructor(message, statusCode) { + super(message); + this.statusCode = statusCode; + } +} + +module.exports = AppError; diff --git a/server/__mocks__/bcrypt.js b/server/__mocks__/bcrypt.js new file mode 100644 index 0000000..e03367e --- /dev/null +++ b/server/__mocks__/bcrypt.js @@ -0,0 +1,6 @@ +const mockBcrypt = { + hash: jest.fn().mockResolvedValue('hashedPassword'), + compare: jest.fn().mockResolvedValue(true), +}; + +module.exports = mockBcrypt; diff --git a/server/__mocks__/db.js b/server/__mocks__/db.js new file mode 100644 index 0000000..a8abb72 --- /dev/null +++ b/server/__mocks__/db.js @@ -0,0 +1,20 @@ +class MockDBConnection { + constructor() { + this.db = jest.fn().mockReturnThis(); + this.collection = jest.fn().mockReturnThis(); + this.insertOne = jest.fn(); + this.findOne = jest.fn(); + this.updateOne = jest.fn(); + this.deleteOne = jest.fn(); + } + + async connect() { + // Simulate successful connection + } + + getConnection() { + return this; + } +} + +module.exports = MockDBConnection; diff --git a/server/__mocks__/folders.js b/server/__mocks__/folders.js new file mode 100644 index 0000000..9bc3abe --- /dev/null +++ b/server/__mocks__/folders.js @@ -0,0 +1,8 @@ +const mockFolders = { + create: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), +}; + +module.exports = mockFolders; diff --git a/server/__tests__/folders.test.js b/server/__tests__/folders.test.js new file mode 100644 index 0000000..8e4bbdd --- /dev/null +++ b/server/__tests__/folders.test.js @@ -0,0 +1,441 @@ +const { create } = require('../middleware/jwtToken'); +const Folders = require('../models/folders'); +const ObjectId = require('mongodb').ObjectId; +const Quizzes = require('../models/quiz'); + +describe('Folders', () => { + let folders; + let db; + let collection; + let quizzes; + + beforeEach(() => { + jest.clearAllMocks(); // Clear any previous mock calls + + // Mock the collection object + collection = { + findOne: jest.fn(), + insertOne: jest.fn(), + find: jest.fn().mockReturnValue({ toArray: jest.fn() }), // Mock the find method + deleteOne: jest.fn(), + deleteMany: jest.fn(), + updateOne: jest.fn(), + }; + + // Mock the database connection + db = { + connect: jest.fn(), + getConnection: jest.fn().mockReturnThis(), // Add getConnection method + collection: jest.fn().mockReturnValue(collection), + }; + + quizzes = new Quizzes(db); + folders = new Folders(db, quizzes); + + }); + + // create + describe('create', () => { + it('should create a new folder and return the new folder ID', async () => { + const title = 'Test Folder'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + collection.insertOne.mockResolvedValue({ insertedId: new ObjectId() }); + + const result = await folders.create(title, '12345'); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId: '12345' }); + expect(collection.insertOne).toHaveBeenCalledWith(expect.objectContaining({ title, userId: '12345' })); + expect(result).toBeDefined(); + }); + + // throw an error if userId is undefined + it('should throw an error if userId is undefined', async () => { + const title = 'Test Folder'; + + await expect(folders.create(title, undefined)).rejects.toThrow('Missing required parameter(s)'); + + expect(db.connect).not.toHaveBeenCalled(); + }); + + it('should throw an error if the folder already exists', async () => { + const title = 'Existing Folder'; + const userId = '66fc70bea1b9e87655cf17c9'; + + // Mock the database response of a found folder + collection.findOne.mockResolvedValue( + // real result from mongosh + { + _id: ObjectId.createFromHexString('66fd33fd81758a882ce99aae'), + userId: userId, + title: title, + created_at: new Date('2024-10-02T11:52:29.797Z') + } + ); + + await expect(folders.create(title, userId)).rejects.toThrow('Folder already exists'); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId: userId }); + }); + }); + + // getUserFolders + describe('getUserFolders', () => { + it('should return all folders for a user', async () => { + const userId = '12345'; + const userFolders = [ + { title: 'Folder 1', userId }, + { title: 'Folder 2', userId }, + ]; + + // Mock the database response + collection.find().toArray.mockResolvedValue(userFolders); + + const result = await folders.getUserFolders(userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.find).toHaveBeenCalledWith({ userId }); + expect(result).toEqual(userFolders); + }); + }); + + // getOwner + describe('getOwner', () => { + it('should return the owner of a folder', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue({ userId }); + + const result = await folders.getOwner(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toBe(userId); + }); + }); + + // write a test for getContent + describe('getContent', () => { + it('should return the content of a folder', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const content = [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ]; + + // Mock the database response + collection.find().toArray.mockResolvedValue(content); + + const result = await folders.getContent(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('files'); + expect(collection.find).toHaveBeenCalledWith({ folderId }); + expect(result).toEqual(content); + }); + + it('should return an empty array if the folder has no content', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.find().toArray.mockResolvedValue([]); + + const result = await folders.getContent(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('files'); + expect(collection.find).toHaveBeenCalledWith({ folderId }); + expect(result).toEqual([]); + }); + }); + + // delete + describe('delete', () => { + it('should delete a folder and return true', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteOne.mockResolvedValue({ deletedCount: 1 }); + + + // Mock the folders.quizModel.deleteQuizzesByFolderId() + jest.spyOn(quizzes, 'deleteQuizzesByFolderId').mockResolvedValue(true); + + const result = await folders.delete(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.deleteOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toBe(true); + }); + + it('should return false if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteOne.mockResolvedValue({ deletedCount: 0 }); + + const result = await folders.delete(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.deleteOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toBe(false); + }); + }); + + // rename + describe('rename', () => { + it('should rename a folder and return true', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newTitle = 'New Folder Name'; + + // Mock the database response + collection.updateOne.mockResolvedValue({ modifiedCount: 1 }); + + const result = await folders.rename(folderId, newTitle); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } }); + expect(result).toBe(true); + }); + + it('should return false if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newTitle = 'New Folder Name'; + + // Mock the database response + collection.updateOne.mockResolvedValue({ modifiedCount: 0 }); + + const result = await folders.rename(folderId, newTitle); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } }); + expect(result).toBe(false); + }); + }); + + // duplicate + describe('duplicate', () => { + it('should duplicate a folder and return the new folder ID', async () => { + const userId = '12345'; + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const sourceFolder = {title: 'SourceFolder', userId: userId, content: []}; + const duplicatedFolder = {title: 'SourceFolder (1)', userId: userId, created_at: expect.any(Date), content: []}; + + // Mock the database responses for the folder and the new folder (first one is found, second one is null) + // mock the findOne method + jest.spyOn(collection, 'findOne') + .mockResolvedValueOnce(sourceFolder) // source file exists + .mockResolvedValueOnce(null); // new name is not found + + // Mock the folder create method + const createSpy = jest.spyOn(folders, 'create').mockResolvedValue(new ObjectId()); + + // mock the folder.getContent method + jest.spyOn(folders, 'getContent').mockResolvedValue([{ title: 'Quiz 1', content: [] }]); + + // Mock the quizzes.create method + jest.spyOn(quizzes, 'create').mockResolvedValue(new ObjectId()); + + const result = await folders.duplicate(folderId, userId); + + expect(db.collection).toHaveBeenCalledWith('folders'); + + // expect folders.create method was called + expect(createSpy).toHaveBeenCalledWith(duplicatedFolder.title, userId); + // expect the getContent method was called + expect(folders.getContent).toHaveBeenCalledWith(folderId); + // expect the quizzes.create method was called + expect(quizzes.create).toHaveBeenCalledWith('Quiz 1', [], expect.any(String), userId); + + expect(result).toBeDefined(); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response for the source + collection.findOne.mockResolvedValue(null); + + await expect(folders.duplicate(folderId, '54321')).rejects.toThrow(`Folder ${folderId} not found`); + + // expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId), userId: '54321' }); + }); + }); + + describe('folderExists', () => { + it('should return true if folder exists', async () => { + const title = 'Test Folder'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue({ title, userId }); + + const result = await folders.folderExists(title, userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId }); + expect(result).toBe(true); + }); + + it('should return false if folder does not exist', async () => { + const title = 'Nonexistent Folder'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + + const result = await folders.folderExists(title, userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId }); + expect(result).toBe(false); + }); + }); + + describe('copy', () => { + it('should copy a folder and return the new folder ID', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + const newFolderId = new ObjectId(); + // Mock some quizzes that are in folder.content + const sourceFolder = { + title: 'Test Folder', + content: [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ], + }; + + // Mock the response from getFolderWithContent + jest.spyOn(folders, 'getFolderWithContent').mockResolvedValue(sourceFolder); + jest.spyOn(folders, 'create').mockResolvedValue(newFolderId); + // Mock the response from Quiz.createQuiz + jest.spyOn(quizzes, 'create').mockImplementation(() => {}); + + const result = await folders.copy(folderId, userId); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + // expect(collection.insertOne).toHaveBeenCalledWith(expect.objectContaining({ userId })); + expect(result).toBe(newFolderId); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + + // Mock the response from getFolderWithContent + jest.spyOn(folders, 'getFolderWithContent').mockImplementation(() => { + throw new Error(`Folder ${folderId} not found`); + }); + + await expect(folders.copy(folderId, userId)).rejects.toThrow(`Folder ${folderId} not found`); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); + + // write a test for getFolderWithContent + describe('getFolderWithContent', () => { + it('should return a folder with content', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const folder = { + _id: new ObjectId(folderId), + title: 'Test Folder', + }; + const content = { + content : [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ]}; + + // Mock the response from getFolderById + jest.spyOn(folders, 'getFolderById').mockResolvedValue(folder); + + // Mock the response from getContent + jest.spyOn(folders, 'getContent').mockResolvedValue(content); + + const result = await folders.getFolderWithContent(folderId); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toEqual({ + ...folder, + content: content + }); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // // Mock the database response + // collection.findOne.mockResolvedValue(null); + + // Mock getFolderById to throw an error + jest.spyOn(folders, 'getFolderById').mockImplementation(() => { + throw new Error(`Folder ${folderId} not found`); + }); + + await expect(folders.getFolderWithContent(folderId)).rejects.toThrow(`Folder ${folderId} not found`); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); + + // write a test for getFolderById + describe('getFolderById', () => { + it('should return a folder by ID', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const folder = { + _id: new ObjectId(folderId), + title: 'Test Folder', + }; + + // Mock the database response + collection.findOne.mockResolvedValue(folder); + + const result = await folders.getFolderById(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toEqual(folder); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + + await expect(folders.getFolderById(folderId)).resolves.toThrow(`Folder ${folderId} not found`); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); +}); diff --git a/server/__tests__/image.test.js b/server/__tests__/image.test.js index e2ed81c..41308e7 100644 --- a/server/__tests__/image.test.js +++ b/server/__tests__/image.test.js @@ -1,11 +1,11 @@ -const request = require('supertest'); -const app = require('../app.js'); -// const app = require('../routers/images.js'); -const { response } = require('express'); +// const request = require('supertest'); +// const app = require('../app.js'); +// // const app = require('../routers/images.js'); +// const { response } = require('express'); -const BASE_URL = '/image' +// const BASE_URL = '/image' -describe("POST /upload", () => { +describe.skip("POST /upload", () => { describe("when the jwt is not sent", () => { @@ -44,7 +44,7 @@ describe("POST /upload", () => { }) -describe("GET /get", () => { +describe.skip("GET /get", () => { describe("when not give id", () => { @@ -61,4 +61,4 @@ describe("GET /get", () => { }) -}) \ No newline at end of file +}) diff --git a/server/__tests__/quizzes.test.js b/server/__tests__/quizzes.test.js new file mode 100644 index 0000000..086ab7f --- /dev/null +++ b/server/__tests__/quizzes.test.js @@ -0,0 +1,347 @@ +const { ObjectId } = require('mongodb'); +const Quizzes = require('../models/quiz'); // Adjust the path as necessary + +describe('Quizzes', () => { + let db; + let quizzes; + let collection; + + beforeEach(() => { + jest.clearAllMocks(); // Clear any previous mock calls + + // Mock the collection object + collection = { + findOne: jest.fn(), + insertOne: jest.fn(), + find: jest.fn().mockReturnValue({ toArray: jest.fn() }), // Mock the find method + deleteOne: jest.fn(), + deleteMany: jest.fn(), + updateOne: jest.fn(), + getContent: jest.fn(), + }; + + // Mock the database connection + db = { + connect: jest.fn(), + getConnection: jest.fn().mockReturnValue({ + collection: jest.fn().mockReturnValue(collection), + }), + }; + + // Initialize the Quiz model with the mocked db + quizzes = new Quizzes(db); + }); + + describe('create', () => { + it('should create a new quiz if it does not exist', async () => { + const title = 'Test Quiz'; + const content = 'This is a test quiz.'; + const folderId = '507f1f77bcf86cd799439011'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + collection.insertOne.mockResolvedValue({ insertedId: new ObjectId() }); + + const result = await quizzes.create(title, content, folderId, userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.findOne).toHaveBeenCalledWith({ title, folderId, userId }); + expect(collection.insertOne).toHaveBeenCalledWith(expect.objectContaining({ + folderId, + userId, + title, + content, + created_at: expect.any(Date), + updated_at: expect.any(Date), + })); + expect(result).not.toBeNull(); + }); + + it('should throw exception if the quiz already exists', async () => { + const title = 'Test Quiz'; + const content = 'This is a test quiz.'; + const folderId = '507f1f77bcf86cd799439011'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue({ title }); + + await expect(quizzes.create(title, content, folderId, userId)).rejects.toThrow(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`); + }); + }); + + describe('getOwner', () => { + it('should return the owner of the quiz', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue({ userId }); + + const result = await quizzes.getOwner(quizId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.findOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) }); + expect(result).toBe(userId); + }); + }); + + describe('getContent', () => { + it('should return the content of the quiz', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const content = 'This is a test quiz.'; + + // Mock the database response + collection.findOne.mockResolvedValue({ content }); + + const result = await quizzes.getContent(quizId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.findOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) }); + expect(result).toEqual({ content }); + }); + }); + + describe('delete', () => { + it('should delete the quiz', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteOne.mockResolvedValue({deletedCount: 1}); + + await quizzes.delete(quizId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.deleteOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) }); + }); + + it('should return false if the quiz does not exist', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteOne.mockResolvedValue({deletedCount: 0}); + + const result = await quizzes.delete(quizId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.deleteOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) }); + expect(result).toBe(false); + }); + }); + + // deleteQuizzesByFolderId + describe('deleteQuizzesByFolderId', () => { + it('should delete all quizzes in a folder', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteMany.mockResolvedValue({deletedCount: 2}); + + await quizzes.deleteQuizzesByFolderId(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.deleteMany).toHaveBeenCalledWith({ folderId }); + }); + + it('should return false if no quizzes are deleted', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.deleteMany.mockResolvedValue({deletedCount: 0}); + + const result = await quizzes.deleteQuizzesByFolderId(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.deleteMany).toHaveBeenCalledWith({ folderId }); + expect(result).toBe(false); + }); + }); + + // update + describe('update', () => { + it('should update the title and content of the quiz', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newTitle = 'Updated Quiz'; + const newContent = 'This is an updated quiz.'; + + // Mock the database response + collection.updateOne.mockResolvedValue({modifiedCount: 1}); + + await quizzes.update(quizId, newTitle, newContent); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.updateOne).toHaveBeenCalledWith( + { _id: ObjectId.createFromHexString(quizId) }, + { $set: { title: newTitle, content: newContent, updated_at: expect.any(Date) } } + ); + }); + + it('should return false if the quiz does not exist', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newTitle = 'Updated Quiz'; + const newContent = 'This is an updated quiz.'; + + // Mock the database response + collection.updateOne.mockResolvedValue({modifiedCount: 0}); + + const result = await quizzes.update(quizId, newTitle, newContent); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.updateOne).toHaveBeenCalledWith( + { _id: ObjectId.createFromHexString(quizId) }, + { $set: { title: newTitle, content: newContent, updated_at: expect.any(Date) } } + ); + expect(result).toBe(false); + }); + }); + + // move + describe('move', () => { + it('should move the quiz to a new folder', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newFolderId = '507f1f77bcf86cd799439011'; + + // Mock the database response + collection.updateOne.mockResolvedValue({modifiedCount: 1}); + + await quizzes.move(quizId, newFolderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.updateOne).toHaveBeenCalledWith( + { _id: ObjectId.createFromHexString(quizId) }, + { $set: { folderId: newFolderId } } + ); + }); + + it('should return false if the quiz does not exist', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const newFolderId = '507f1f77bcf86cd799439011'; + + // Mock the database response + collection.updateOne.mockResolvedValue({modifiedCount: 0}); + + const result = await quizzes.move(quizId, newFolderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.getConnection).toHaveBeenCalled(); + expect(collection.updateOne).toHaveBeenCalledWith( + { _id: ObjectId.createFromHexString(quizId) }, + { $set: { folderId: newFolderId } } + ); + expect(result).toBe(false); + }); + }); + + // duplicate + describe('duplicate', () => { + + it('should duplicate the quiz and return the new quiz ID', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000)); // Corrected ObjectId creation + const sourceQuiz = { + title: 'Test Quiz', + content: 'This is a test quiz.', + }; + + const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId); + // mock the findOne method + jest.spyOn(collection, 'findOne') + .mockResolvedValueOnce(sourceQuiz) // source quiz exists + .mockResolvedValueOnce(null); // new name is not found + + const result = await quizzes.duplicate(quizId, userId); + + expect(result).toBe(newQuizId); + + // Ensure mocks were called correctly + expect(createMock).toHaveBeenCalledWith( + sourceQuiz.title + ' (1)', + sourceQuiz.content, + undefined, + userId + ); + }); + + // Add test case for quizExists (name with number in parentheses) + it('should create a new title if the quiz title already exists and ends with " (1)"', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000)); + const sourceQuiz = { + title: 'Test Quiz (1)', + content: 'This is a test quiz.', + }; + + const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId); + // mock the findOne method + jest.spyOn(collection, 'findOne') + .mockResolvedValueOnce(sourceQuiz) // source quiz exists + .mockResolvedValueOnce(null); // new name is not found + + const result = await quizzes.duplicate(quizId, userId); + + expect(result).toBe(newQuizId); + + // Ensure mocks were called correctly + expect(createMock).toHaveBeenCalledWith( + 'Test Quiz (2)', + sourceQuiz.content, + undefined, + userId + ); + }); + + // test case for duplication of "C (1)" but "C (2)" already exists, so it should create "C (3)" + it('should create a new title if the quiz title already exists and ends with " (n)" but the incremented n also exists', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000)); + const sourceQuiz = { + title: 'Test Quiz (1)', + content: 'This is a test quiz.', + }; + + const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId); + + // mock the findOne method + jest.spyOn(collection, 'findOne') + .mockResolvedValueOnce(sourceQuiz) // source quiz exists + .mockResolvedValueOnce({ title: 'Test Quiz (2)' }) // new name collision + .mockResolvedValueOnce(null); // final new name is not found + + const result = await quizzes.duplicate(quizId, userId); + + expect(result).toBe(newQuizId); + + // Ensure mocks were called correctly + expect(createMock).toHaveBeenCalledWith( + 'Test Quiz (3)', + sourceQuiz.content, + undefined, + userId + ); + }); + + it('should throw an error if the quiz does not exist', async () => { + const quizId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + + // Mock the response from getContent + jest.spyOn(quizzes, 'getContent').mockResolvedValue(null); + + await expect(quizzes.duplicate(quizId, userId)).rejects.toThrow(); + }); + }); +}); diff --git a/server/__tests__/socket.test.js b/server/__tests__/socket.test.js index 141a31a..95c404f 100644 --- a/server/__tests__/socket.test.js +++ b/server/__tests__/socket.test.js @@ -5,7 +5,8 @@ const { setupWebsocket } = require("../socket/socket"); process.env.NODE_ENV = "test"; -const BACKEND_PORT = 4400; +// pick a random port number for testing +const BACKEND_PORT = Math.ceil(Math.random() * 1000 + 3000); const BACKEND_URL = "http://localhost"; const BACKEND_API = `${BACKEND_URL}:${BACKEND_PORT}`; diff --git a/server/__tests__/users.test.js b/server/__tests__/users.test.js new file mode 100644 index 0000000..21c6dfa --- /dev/null +++ b/server/__tests__/users.test.js @@ -0,0 +1,86 @@ +const Users = require('../models/users'); +const bcrypt = require('bcrypt'); +const Quizzes = require('../models/quiz'); +const Folders = require('../models/folders'); +const AppError = require('../middleware/AppError'); +const { ObjectId } = require('mongodb'); + +jest.mock('bcrypt'); +jest.mock('../middleware/AppError'); +jest.mock('../models/folders'); + +describe('Users', () => { + let users; + let db; + + beforeEach(() => { + jest.clearAllMocks(); // Clear any previous mock calls + + // Mock the database connection + db = { + connect: jest.fn(), + getConnection: jest.fn().mockReturnThis(), // Add getConnection method + collection: jest.fn().mockReturnThis(), + findOne: jest.fn(), + insertOne: jest.fn().mockResolvedValue({ insertedId: new ObjectId() }), // Mock insertOne to return an ObjectId + updateOne: jest.fn(), + deleteOne: jest.fn(), + }; + + const quizModel = new Quizzes(db); + const foldersModel = new Folders(db, quizModel); + + users = new Users(db, foldersModel); + }); + + it('should register a new user', async () => { + db.collection().findOne.mockResolvedValue(null); // No user found + db.collection().insertOne.mockResolvedValue({ insertedId: new ObjectId() }); + bcrypt.hash.mockResolvedValue('hashedPassword'); + users.folders.create.mockResolvedValue(true); + + const email = 'test@example.com'; + const password = 'password123'; + const result = await users.register(email, password); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection().findOne).toHaveBeenCalledWith({ email }); + expect(bcrypt.hash).toHaveBeenCalledWith(password, 10); + expect(db.collection().insertOne).toHaveBeenCalledWith({ + email, + password: 'hashedPassword', + created_at: expect.any(Date), + }); + expect(users.folders.create).toHaveBeenCalledWith('Dossier par Défaut', expect.any(String)); + expect(result.insertedId).toBeDefined(); // Ensure result has insertedId + }); + + // it('should update the user password', async () => { + // db.collection().updateOne.mockResolvedValue({ modifiedCount: 1 }); + // bcrypt.hash.mockResolvedValue('hashedPassword'); + + // const email = 'test@example.com'; + // const newPassword = 'newPassword123'; + // const result = await users.updatePassword(email, newPassword); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection().updateOne).toHaveBeenCalledWith( + // { email }, + // { $set: { password: 'hashedPassword' } } + // ); + // expect(result).toEqual(newPassword); + // }); + + // it('should delete a user', async () => { + // db.collection().deleteOne.mockResolvedValue({ deletedCount: 1 }); + + // const email = 'test@example.com'; + // const result = await users.delete(email); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection().deleteOne).toHaveBeenCalledWith({ email }); + // expect(result).toBe(true); + // }); + + // Add more tests as needed +}); diff --git a/server/app.js b/server/app.js index 3af6336..0095216 100644 --- a/server/app.js +++ b/server/app.js @@ -1,21 +1,57 @@ // Import API const express = require("express"); const http = require("http"); -const dotenv = require('dotenv') +const dotenv = require('dotenv'); -// Import Sockets -const { setupWebsocket } = require("./socket/socket"); -const { Server } = require("socket.io"); +// instantiate the db +const db = require('./config/db.js'); +// instantiate the models +const quiz = require('./models/quiz.js'); +const quizModel = new quiz(db); +const folders = require('./models/folders.js'); +const foldersModel = new folders(db, quizModel); +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); -//import routers +// Instantiate the controllers +const QuizProviderOptions = { + provider: 'docker' +}; + +// instantiate the controllers +const usersController = require('./controllers/users.js'); +const usersControllerInstance = new usersController(userModel); +const foldersController = require('./controllers/folders.js'); +const foldersControllerInstance = new foldersController(foldersModel); +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 AuthManager = require('./auth/auth-manager.js') -const authRouter = require('./routers/auth.js') +const imagesRouter = require('./routers/images.js'); +const AuthManager = require('./auth/auth-manager.js'); +const authRouter = require('./routers/auth.js'); +const roomRouter = require('./routers/rooms.js'); +const healthRouter = require('./routers/health.js'); -// Setup environement +// Setup environment dotenv.config(); // Setup urls from configs @@ -23,7 +59,6 @@ const use_ports = (process.env['USE_PORTS'] || 'false').toLowerCase() == "true" process.env['FRONTEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['FRONTEND_PORT']}`:"") process.env['BACKEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['PORT']}`:"") -const db = require('./config/db.js'); const errorHandler = require("./middleware/errorHandler.js"); // Start app @@ -31,22 +66,10 @@ const app = express(); const cors = require("cors"); const bodyParser = require('body-parser'); -const configureServer = (httpServer) => { - return new Server(httpServer, { - path: "/socket.io", - cors: { - origin: "*", - methods: ["GET", "POST"], - credentials: true, - }, - }); -}; +let server = http.createServer(app); +let isDev = process.env.NODE_ENV === 'development'; +console.log(`Environnement: ${process.env.NODE_ENV} (${isDev ? 'dev' : 'prod'})`); -// Start sockets -const server = http.createServer(app); -const io = configureServer(server); - -setupWebsocket(io); app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); @@ -69,21 +92,23 @@ app.use(session({ authManager = new AuthManager(app) app.use(errorHandler) +app.use('/api/room', roomRouter); +app.use('/health', healthRouter); + +app.use(errorHandler); // Start server async function start() { - const port = process.env.PORT || 4400; // Check DB connection - await db.connect() + await db.connect(); db.getConnection(); console.log(`Connexion MongoDB établie`); server.listen(port, () => { console.log(`Serveur écoutant sur le port ${port}`); }); - } start(); diff --git a/server/controllers/folders.js b/server/controllers/folders.js index 17a5039..1b0c1b3 100644 --- a/server/controllers/folders.js +++ b/server/controllers/folders.js @@ -1,174 +1,170 @@ //controller -const model = require('../models/folders.js'); - const AppError = require('../middleware/AppError.js'); const { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, FOLDER_NOT_FOUND, FOLDER_ALREADY_EXISTS, GETTING_FOLDER_ERROR, DELETE_FOLDER_ERROR, UPDATE_FOLDER_ERROR, MOVING_FOLDER_ERROR, DUPLICATE_FOLDER_ERROR, COPY_FOLDER_ERROR } = require('../constants/errorCodes'); +// controllers must use arrow functions to bind 'this' to the class instance in order to access class properties as callbacks in Express class FoldersController { + constructor(foldersModel) { + this.folders = foldersModel; + } + /*** * Basic queries */ - async create(req, res, next) { + create = async (req, res, next) => { try { const { title } = req.body; - + if (!title) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - const result = await model.create(title, req.user.userId); - + + const result = await this.folders.create(title, req.user.userId); + if (!result) { throw new AppError(FOLDER_ALREADY_EXISTS); } - + return res.status(200).json({ message: 'Dossier créé avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async getUserFolders(req, res, next) { - + + getUserFolders = async (req, res, next) => { try { - const folders = await model.getUserFolders(req.user.userId); - + const folders = await this.folders.getUserFolders(req.user.userId); + if (!folders) { throw new AppError(FOLDER_NOT_FOUND); } - + return res.status(200).json({ data: folders }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async getFolderContent(req, res, next) { + + getFolderContent = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const content = await model.getContent(folderId); - + + const content = await this.folders.getContent(folderId); + if (!content) { throw new AppError(GETTING_FOLDER_ERROR); } - + return res.status(200).json({ data: content }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async delete(req, res, next) { + + delete = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.delete(folderId); - + + const result = await this.folders.delete(folderId); + if (!result) { throw new AppError(DELETE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier supprimé avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async rename(req, res, next) { + + rename = async (req, res, next) => { try { const { folderId, newTitle } = req.body; - + if (!folderId || !newTitle) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.rename(folderId, newTitle); - + + const result = await this.folders.rename(folderId, newTitle); + if (!result) { throw new AppError(UPDATE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier mis à jours avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - - async duplicate(req, res, next) { + + duplicate = async (req, res, next) => { try { - const { folderId, } = req.body; - - if (!folderId ) { + const { folderId } = req.body; + + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const userId = req.user.userId; - - const newFolderId = await model.duplicate(folderId, userId); - + + const userId = req.user.userId; + + const newFolderId = await this.folders.duplicate(folderId, userId); + if (!newFolderId) { throw new AppError(DUPLICATE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier dupliqué avec succès.', newFolderId: newFolderId @@ -177,30 +173,30 @@ class FoldersController { return next(error); } } - - async copy(req, res, next) { + + copy = async (req, res, next) => { try { const { folderId, newTitle } = req.body; - + if (!folderId || !newTitle) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - + const userId = req.user.userId; // Assuming userId is obtained from authentication - - const newFolderId = await model.copy(folderId, userId); - + + const newFolderId = await this.folders.copy(folderId, userId); + if (!newFolderId) { throw new AppError(COPY_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier copié avec succès.', newFolderId: newFolderId @@ -210,27 +206,27 @@ class FoldersController { } } - async getFolderById(req, res, next) { + getFolderById = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const folder = await model.getFolderById(folderId); - + + const folder = await this.folders.getFolderById(folderId); + if (!folder) { throw new AppError(FOLDER_NOT_FOUND); } - + return res.status(200).json({ data: folder }); @@ -238,8 +234,8 @@ class FoldersController { return next(error); } } - - async folderExists(req, res, next) { + + folderExists = async (req, res, next) => { try { const { title } = req.body; @@ -247,10 +243,10 @@ class FoldersController { throw new AppError(MISSING_REQUIRED_PARAMETER); } - const userId = req.user.userId; + const userId = req.user.userId; // Vérifie si le dossier existe pour l'utilisateur donné - const exists = await model.folderExists(title, userId); + const exists = await this.folders.folderExists(title, userId); return res.status(200).json({ exists: exists @@ -260,9 +256,8 @@ class FoldersController { } } - } -module.exports = new FoldersController; \ No newline at end of file +module.exports = FoldersController; diff --git a/server/controllers/images.js b/server/controllers/images.js index 757961c..b77ed96 100644 --- a/server/controllers/images.js +++ b/server/controllers/images.js @@ -1,56 +1,55 @@ -const model = require('../models/images.js'); - const AppError = require('../middleware/AppError.js'); const { MISSING_REQUIRED_PARAMETER, IMAGE_NOT_FOUND } = require('../constants/errorCodes'); class ImagesController { - async upload(req, res, next) { + constructor(imagesModel) { + this.images = imagesModel; + } + + upload = async (req, res, next) => { try { const file = req.file; - + if (!file) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - const id = await model.upload(file, req.user.userId); - + + const id = await this.images.upload(file, req.user.userId); + return res.status(200).json({ id: id }); - } - catch (error) { + } catch (error) { return next(error); } - - } - - async get(req, res, next) { + }; + + get = async (req, res, next) => { try { const { id } = req.params; - + if (!id) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - const image = await model.get(id); - + + const image = await this.images.get(id); + if (!image) { - throw new AppError(IMAGE_NOT_FOUND) + throw new AppError(IMAGE_NOT_FOUND); } - + // Set Headers for display in browser res.setHeader('Content-Type', image.mime_type); res.setHeader('Content-Disposition', 'inline; filename=' + image.file_name); res.setHeader('Accept-Ranges', 'bytes'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); return res.send(image.file_content); - } - catch (error) { + } catch (error) { return next(error); } - } + }; } -module.exports = new ImagesController; \ No newline at end of file +module.exports = ImagesController; diff --git a/server/controllers/quiz.js b/server/controllers/quiz.js index 8b8b247..e293bf6 100644 --- a/server/controllers/quiz.js +++ b/server/controllers/quiz.js @@ -1,5 +1,3 @@ -const model = require('../models/quiz.js'); -const folderModel = require('../models/folders.js'); const emailer = require('../config/email.js'); const AppError = require('../middleware/AppError.js'); @@ -7,184 +5,181 @@ const { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, QUIZ_NOT_FOUND, FOLDER_NOT_ class QuizController { - async create(req, res, next) { + constructor(quizModel, foldersModel) { + this.folders = foldersModel; + this.quizzes = quizModel; + } + + 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 folderModel.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.create(title, content, folderId, req.user.userId); - + + const result = await this.quizzes.create(title, content, folderId, req.user.userId); + if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); } - + return res.status(200).json({ message: 'Quiz créé avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } - } - - async get(req, res, next) { + }; + + get = async (req, res, next) => { try { const { quizId } = req.params; - + if (!quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - - const content = await model.getContent(quizId); - + + 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) { + + } catch (error) { return next(error); } - } - - async delete(req, res, next) { + }; + + 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 model.getOwner(quizId); - + const owner = await this.quizzes.getOwner(quizId); + if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - - const result = await model.delete(quizId); - + + 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) { + + } catch (error) { return next(error); } - } - - async update(req, res, next) { + }; + + 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 model.getOwner(quizId); - + const owner = await this.quizzes.getOwner(quizId); + if (owner != req.user.userId) { throw new AppError(QUIZ_NOT_FOUND); } - - const result = await model.update(quizId, newTitle, newContent); - + + 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) { + + } catch (error) { return next(error); } - } - - async move(req, res, next) { + }; + + 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 model.getOwner(quizId); - + 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 folderModel.getOwner(newFolderId); - + const folderOwner = await this.folders.getOwner(newFolderId); + if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.move(quizId, newFolderId); - + + 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) { + + } catch (error) { return next(error); } - - } - + }; - async copy(req, res, next) { + 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(); - // const quiztoduplicate = await conn.collection('quiz').findOne({ _id: new ObjectId(quizId) }); + // const quiztoduplicate = await conn.collection('quiz').findOne({ _id: ObjectId.createFromHexString(quizId) }); // if (!quiztoduplicate) { // throw new Error("Quiz non trouvé"); // } @@ -192,121 +187,119 @@ class QuizController { // //Suppression du id du quiz pour ne pas le répliquer // delete quiztoduplicate._id; // //Ajout du duplicata - // await conn.collection('quiz').insertOne({ ...quiztoduplicate, userId: new ObjectId(newUserId) }); + // 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)); // } // res.status(500).json(Response.serverError(error.message)); // } - } - - async deleteQuizzesByFolderId(req, res, next) { + }; + + 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 Quiz.deleteQuizzesByFolderId(folderId); - + return res.status(200).json({ message: 'Quizzes deleted successfully.' }); } catch (error) { return next(error); } - } - - async duplicate(req, res, next) { - const { quizId } = req.body; - + }; + + duplicate = async (req, res, next) => { + const { quizId } = req.body; + try { - const newQuizId = await model.duplicate(quizId,req.user.userId); + const newQuizId = await this.quizzes.duplicate(quizId, req.user.userId); res.status(200).json({ success: true, newQuizId }); } catch (error) { return next(error); } - } - - async quizExists(title, userId) { + }; + + quizExists = async (title, userId) => { try { - const existingFile = await model.quizExists(title, userId); + const existingFile = await this.quizzes.quizExists(title, userId); return existingFile !== null; } catch (error) { throw new AppError(GETTING_QUIZ_ERROR); } - } - - async Share(req, res, next) { + }; + + share = async (req, res, next) => { try { const { quizId, email } = req.body; - if ( !quizId || !email) { + 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) { + } catch (error) { return next(error); } - } + }; - async getShare(req, res, next) { + getShare = async (req, res, next) => { try { const { quizId } = req.params; - if ( !quizId ) { + if (!quizId) { throw new AppError(MISSING_REQUIRED_PARAMETER); - } - - const content = await model.getContent(quizId); - + } + + 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) { + } catch (error) { return next(error); } - } - - async receiveShare(req, res, next) { + }; + + receiveShare = async (req, res, next) => { try { const { quizId, folderId } = req.body; if (!quizId || !folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - const folderOwner = await folderModel.getOwner(folderId); + + const folderOwner = await this.folders.getOwner(folderId); if (folderOwner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - const content = await model.getContent(quizId); + const content = await this.quizzes.getContent(quizId); if (!content) { throw new AppError(GETTING_QUIZ_ERROR); } - const result = await model.create(content.title, content.content, folderId, req.user.userId); + const result = await this.quizzes.create(content.title, content.content, folderId, req.user.userId); if (!result) { throw new AppError(QUIZ_ALREADY_EXISTS); } @@ -314,13 +307,11 @@ class QuizController { return res.status(200).json({ message: 'Quiz partagé reçu.' }); - } - catch (error) { + } catch (error) { return next(error); } - } - + }; } -module.exports = new QuizController; \ No newline at end of file +module.exports = QuizController; diff --git a/server/controllers/rooms.js b/server/controllers/rooms.js new file mode 100644 index 0000000..8f0382f --- /dev/null +++ b/server/controllers/rooms.js @@ -0,0 +1,94 @@ +const {Room} = require('../models/room.js'); + +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'); + +const NB_CODE_CHARS = 6; +const NB_MS_UPDATE_ROOM = 1000; +const NB_MS_CLEANUP = 30000; + +class RoomsController { + constructor(options = {}, roomRepository) { + this.provider = this.createProvider( + options.provider || process.env.ROOM_PROVIDER || 'cluster', + options.providerOptions, + roomRepository + ); + this.roomRepository = roomRepository; + this.setupTasks(); + } + + 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(`Type d'approvisionement inconnu: ${type}`); + } + } + + async setupTasks(){ + await this.provider.syncInstantiatedRooms(); + // Update rooms + setInterval(() => { + this.provider.updateRoomsInfo().catch(console.error); + }, NB_MS_UPDATE_ROOM); + + // Cleanup rooms + setInterval(() => { + this.provider.cleanup().catch(console.error); + }, NB_MS_CLEANUP); + } + + async createRoom(options = {}) { + let roomIdValid = false + let roomId; + + while(!roomIdValid){ + roomId = options.roomId || this.generateRoomId(); + roomIdValid = !(await this.provider.getRoomInfo(roomId)); + } + + return await this.provider.createRoom(roomId,options); + } + + async updateRoom(roomId, info) { + return await this.provider.updateRoomInfo(roomId, {}); + } + + 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() { + const characters = "0123456789"; + let result = ""; + for (let i = 0; i < NB_CODE_CHARS; i++) { + result += characters.charAt( + Math.floor(Math.random() * characters.length) + ); + } + return result; + } +} + +module.exports = RoomsController; diff --git a/server/controllers/users.js b/server/controllers/users.js index 097fcdc..66f43de 100644 --- a/server/controllers/users.js +++ b/server/controllers/users.js @@ -1,42 +1,43 @@ const emailer = require('../config/email.js'); -const model = require('../models/users.js'); const jwt = require('../middleware/jwtToken.js'); const AppError = require('../middleware/AppError.js'); const { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR, DELETE_USER_ERROR } = require('../constants/errorCodes'); +// controllers must use arrow functions to bind 'this' to the class instance in order to access class properties as callbacks in Express class UsersController { + constructor(userModel) { + this.users = userModel; + } async delete(req, res, next) { try { const { email, password } = req.body; - + if (!email || !password) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // verify creds first - const user = await model.login(email, password); - + const user = await this.users.login(email, password); + if (!user) { throw new AppError(LOGIN_CREDENTIALS_ERROR); } - - const result = await model.delete(email) - + + const result = await this.users.delete(email); + if (!result) { - throw new AppError(DELETE_USER_ERROR) + throw new AppError(DELETE_USER_ERROR); } - + return res.status(200).json({ message: 'Utilisateur supprimé avec succès' }); - } - catch (error) { + } catch (error) { return next(error); } } - } -module.exports = new UsersController; \ No newline at end of file +module.exports = UsersController; diff --git a/server/middleware/jwtToken.js b/server/middleware/jwtToken.js index 75ad458..5ce44fa 100644 --- a/server/middleware/jwtToken.js +++ b/server/middleware/jwtToken.js @@ -22,7 +22,7 @@ class Token { if (error) { throw new AppError(UNAUTHORIZED_INVALID_TOKEN) } - + req.user = payload; }); diff --git a/server/models/folders.js b/server/models/folders.js index 5ecfca5..5ddc225 100644 --- a/server/models/folders.js +++ b/server/models/folders.js @@ -1,19 +1,31 @@ //model -const db = require('../config/db.js') -const { ObjectId } = require('mongodb'); -const Quiz = require('./quiz.js'); +const ObjectId = require('mongodb').ObjectId; +const { generateUniqueTitle } = require('./utils'); class Folders { + constructor(db, quizModel) { + this.db = db; + this.quizModel = quizModel; + } async create(title, userId) { - await db.connect() - const conn = db.getConnection(); + + console.log("LOG: create", title, userId); + + if (!title || !userId) { + throw new Error('Missing required parameter(s)'); + } + + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); - if (existingFolder) return new Error('Folder already exists'); + if (existingFolder) { + throw new Error('Folder already exists'); + } const newFolder = { userId: userId, @@ -27,8 +39,8 @@ class Folders { } async getUserFolders(userId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -38,19 +50,20 @@ class Folders { } async getOwner(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); - const folder = await foldersCollection.findOne({ _id: new ObjectId(folderId) }); + const folder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId) }); return folder.userId; } + // finds all quizzes in a folder async getContent(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const filesCollection = conn.collection('files'); @@ -60,26 +73,26 @@ class Folders { } async delete(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); - const folderResult = await foldersCollection.deleteOne({ _id: new ObjectId(folderId) }); + const folderResult = await foldersCollection.deleteOne({ _id: ObjectId.createFromHexString(folderId) }); if (folderResult.deletedCount != 1) return false; - await Quiz.deleteQuizzesByFolderId(folderId); + await this.quizModel.deleteQuizzesByFolderId(folderId); return true; } async rename(folderId, newTitle) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); - const result = await foldersCollection.updateOne({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } }) + const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId) }, { $set: { title: newTitle } }) if (result.modifiedCount != 1) return false; @@ -87,69 +100,77 @@ class Folders { } async duplicate(folderId, userId) { + console.log("LOG: duplicate", folderId, userId); + const conn = this.db.getConnection(); + const foldersCollection = conn.collection('folders'); - const sourceFolder = await this.getFolderWithContent(folderId); - - // Check if the new title already exists - let newFolderTitle = sourceFolder.title + "-copie"; - let counter = 1; - - while (await this.folderExists(newFolderTitle, userId)) { - newFolderTitle = `${sourceFolder.title}-copie(${counter})`; - counter++; + const sourceFolder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId), userId: userId }); + if (!sourceFolder) { + throw new Error(`Folder ${folderId} not found`); } - - + + const theUserId = userId; + // Use the utility function to generate a unique title + const newFolderTitle = await generateUniqueTitle(sourceFolder.title, async (title) => { + console.log(`generateUniqueTitle(${title}): userId`, theUserId); + return await foldersCollection.findOne({ title: title, userId: theUserId }); + }); + const newFolderId = await this.create(newFolderTitle, userId); if (!newFolderId) { - throw new Error('Failed to create a duplicate folder.'); + throw new Error('Failed to create duplicate folder'); } - for (const quiz of sourceFolder.content) { - const { title, content } = quiz; - //console.log(title); - //console.log(content); - await Quiz.create(title, content, newFolderId.toString(), userId); + // copy the quizzes from source folder to destination folder + const content = await this.getContent(folderId); + console.log("folders.duplicate: found content", content); + for (const quiz of content) { + console.log("folders.duplicate: creating quiz (copy)", quiz); + const result = await this.quizModel.create(quiz.title, quiz.content, newFolderId.toString(), userId); + if (!result) { + throw new Error('Failed to create duplicate quiz'); + } } return newFolderId; - } async folderExists(title, userId) { - await db.connect(); - const conn = db.getConnection(); + console.log("LOG: folderExists", title, userId); + await this.db.connect(); + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); - return existingFolder !== null; + return !!existingFolder; } async copy(folderId, userId) { - const sourceFolder = await this.getFolderWithContent(folderId); const newFolderId = await this.create(sourceFolder.title, userId); if (!newFolderId) { throw new Error('Failed to create a new folder.'); } for (const quiz of sourceFolder.content) { - await this.createQuiz(quiz.title, quiz.content, newFolderId, userId); + await this.quizModel.create(quiz.title, quiz.content, newFolderId, userId); } return newFolderId; - } + async getFolderById(folderId) { - await db.connect(); - const conn = db.getConnection(); + await this.db.connect(); + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); - const folder = await foldersCollection.findOne({ _id: new ObjectId(folderId) }); + const folder = await foldersCollection.findOne({ _id: ObjectId.createFromHexString(folderId) }); + + if (!folder) return new Error(`Folder ${folderId} not found`); return folder; } @@ -171,4 +192,4 @@ class Folders { } -module.exports = new Folders; +module.exports = Folders; diff --git a/server/models/images.js b/server/models/images.js index 5dfa954..26e5f51 100644 --- a/server/models/images.js +++ b/server/models/images.js @@ -1,8 +1,12 @@ -const db = require('../config/db.js') +//const db = require('../config/db.js') const { ObjectId } = require('mongodb'); class Images { + constructor(db) { + this.db = db; + } + async upload(file, userId) { await db.connect() const conn = db.getConnection(); @@ -28,7 +32,7 @@ class Images { const imagesCollection = conn.collection('images'); - const result = await imagesCollection.findOne({ _id: new ObjectId(id) }); + const result = await imagesCollection.findOne({ _id: ObjectId.createFromHexString(id) }); if (!result) return null; @@ -41,4 +45,4 @@ class Images { } -module.exports = new Images; \ No newline at end of file +module.exports = Images; diff --git a/server/models/quiz.js b/server/models/quiz.js index cb8f5a4..8b34233 100644 --- a/server/models/quiz.js +++ b/server/models/quiz.js @@ -1,17 +1,25 @@ -const db = require('../config/db.js') 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) { - await db.connect() - const conn = db.getConnection(); + console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`); + 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 }) - if (existingQuiz) return null; + if (existingQuiz) { + throw new Error(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`); + } const newQuiz = { folderId: folderId, @@ -23,74 +31,87 @@ class Quiz { } const result = await quizCollection.insertOne(newQuiz); + console.log("quizzes: create insertOne result", result); return result.insertedId; } async getOwner(quizId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const quizCollection = conn.collection('files'); - const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) }); + const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) }); return quiz.userId; } async getContent(quizId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); + const quizCollection = conn.collection('files'); - const quiz = await quizCollection.findOne({ _id: new ObjectId(quizId) }); + const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) }); return quiz; } async delete(quizId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const quizCollection = conn.collection('files'); - const result = await quizCollection.deleteOne({ _id: new ObjectId(quizId) }); + const result = await quizCollection.deleteOne({ _id: ObjectId.createFromHexString(quizId) }); if (result.deletedCount != 1) return false; return true; } async deleteQuizzesByFolderId(folderId) { - await db.connect(); - const conn = db.getConnection(); + await this.db.connect(); + const conn = this.db.getConnection(); const quizzesCollection = conn.collection('files'); // Delete all quizzes with the specified folderId - await quizzesCollection.deleteMany({ folderId: folderId }); + const result = await quizzesCollection.deleteMany({ folderId: folderId }); + return result.deletedCount > 0; } async update(quizId, newTitle, newContent) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const quizCollection = conn.collection('files'); - const result = await quizCollection.updateOne({ _id: new ObjectId(quizId) }, { $set: { title: newTitle, content: newContent } }); - //Ne fonctionne pas si rien n'est chngé dans le quiz - //if (result.modifiedCount != 1) return false; + const result = await quizCollection.updateOne( + { _id: ObjectId.createFromHexString(quizId) }, + { + $set: { + title: newTitle, + content: newContent, + updated_at: new Date() + } + } + ); - return true + return result.modifiedCount === 1; } async move(quizId, newFolderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const quizCollection = conn.collection('files'); - const result = await quizCollection.updateOne({ _id: new ObjectId(quizId) }, { $set: { folderId: newFolderId } }); + const result = await quizCollection.updateOne( + { _id: ObjectId.createFromHexString(quizId) }, + { $set: { folderId: newFolderId } } + ); if (result.modifiedCount != 1) return false; @@ -98,29 +119,31 @@ class Quiz { } async duplicate(quizId, userId) { - - const sourceQuiz = await this.getContent(quizId); - - let newQuizTitle = `${sourceQuiz.title}-copy`; - let counter = 1; - while (await this.quizExists(newQuizTitle, userId)) { - newQuizTitle = `${sourceQuiz.title}-copy(${counter})`; - counter++; + const conn = this.db.getConnection(); + const quizCollection = conn.collection('files'); + + const sourceQuiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId), userId: userId }); + if (!sourceQuiz) { + throw new Error('Quiz not found for quizId: ' + quizId); } - //console.log(newQuizTitle); - const newQuizId = await this.create(newQuizTitle, sourceQuiz.content,sourceQuiz.folderId, userId); + + // Use the utility function to generate a unique title + const newQuizTitle = await generateUniqueTitle(sourceQuiz.title, async (title) => { + return await quizCollection.findOne({ title: title, folderId: sourceQuiz.folderId, userId: userId }); + }); + + const newQuizId = await this.create(newQuizTitle, sourceQuiz.content, sourceQuiz.folderId, userId); if (!newQuizId) { - throw new Error('Failed to create a duplicate quiz.'); + throw new Error('Failed to create duplicate quiz'); } return newQuizId; - } async quizExists(title, userId) { - await db.connect(); - const conn = db.getConnection(); + await this.db.connect(); + const conn = this.db.getConnection(); const filesCollection = conn.collection('files'); const existingFolder = await filesCollection.findOne({ title: title, userId: userId }); @@ -130,4 +153,4 @@ class Quiz { } -module.exports = new Quiz; \ No newline at end of file +module.exports = Quiz; diff --git a/server/models/room.js b/server/models/room.js new file mode 100644 index 0000000..3f1860e --- /dev/null +++ b/server/models/room.js @@ -0,0 +1,87 @@ +class Room { + constructor(id, name, host, nbStudents = 0,) { // Default nbStudents to 0 + this.id = id; + this.name = name; + + if (!host.startsWith('http://') && !host.startsWith('https://')) { + host = 'http://' + host; + } + this.host = host; + + this.nbStudents = nbStudents; + this.mustBeCleaned = false; + } +} + +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(`Érreur: la salle ${room.id} existe déja`); + } + const returnedId = await this.collection.insertOne(room); + return await this.collection.findOne({ _id: returnedId.insertedId }); + } + + async get(id) { + await this.init(); + const existingRoom = await this.collection.findOne({ id: id }); + if (!existingRoom) { + console.warn(`La sale avec l'identifiant ${id} n'as pas été trouvé.`); + return null; + } + return existingRoom; + } + + async getAll() { + await this.init(); + return await this.collection.find().toArray(); + } + + async update(room,roomId = null) { + await this.init(); + + const searchId = roomId ?? room.id; + + const result = await this.collection.updateOne( + { id: searchId }, + { $set: room }, + { upsert: false } + ); + + if (result.modifiedCount === 0) { + if (result.matchedCount > 0) { + return true; // Document exists but no changes needed + } + return false; + } + return true; + } + + async delete(id) { + await this.init(); + const result = await this.collection.deleteOne({ id: id }); + if (result.deletedCount === 0) { + console.warn(`La salle ${id} n'as pas été trouvée pour éffectuer sa suppression.`); + return false; + } + return true; + } +} + +module.exports = { Room, RoomRepository }; diff --git a/server/models/users.js b/server/models/users.js index a392913..f55942c 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -1,11 +1,18 @@ //user -const db = require("../config/db.js"); + const bcrypt = require("bcrypt"); const AppError = require("../middleware/AppError.js"); const { USER_ALREADY_EXISTS } = require("../constants/errorCodes"); const Folders = require("../models/folders.js"); class Users { + + constructor(db, foldersModel) { + // console.log("Users constructor: db", db) + this.db = db; + this.folders = foldersModel; + } + async hashPassword(password) { return await bcrypt.hash(password, 10); } @@ -187,4 +194,4 @@ class Users { } } -module.exports = new Users(); +module.exports = Users; diff --git a/server/models/utils.js b/server/models/utils.js new file mode 100644 index 0000000..8e99a85 --- /dev/null +++ b/server/models/utils.js @@ -0,0 +1,35 @@ +// utils.js +async function generateUniqueTitle(baseTitle, existsCallback) { + console.log(`generateUniqueTitle(${baseTitle})`); + let newTitle = baseTitle; + let counter = 1; + + const titleRegex = /(.*?)(\((\d+)\))?$/; + const match = baseTitle.match(titleRegex); + if (match) { + baseTitle = match[1].trim(); + counter = match[3] ? parseInt(match[3], 10) + 1 : 1; + } + + // If the base title does not end with a parentheses expression, start with "(1)" + if (!match[2]) { + newTitle = `${baseTitle} (${counter})`; + } else { + // else increment the counter in the parentheses expression as a first try + newTitle = `${baseTitle} (${counter})`; + } + + console.log(`first check of newTitle: ${newTitle}`); + + while (await existsCallback(newTitle)) { + counter++; + newTitle = `${baseTitle} (${counter})`; + console.log(`trying newTitle: ${newTitle}`); + } + + return newTitle; +} + +module.exports = { + generateUniqueTitle +}; diff --git a/server/package-lock.json b/server/package-lock.json index 7c21c41..563bd6e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,8 +10,10 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@types/express": "^5.0.0", "bcrypt": "^5.1.1", "cors": "^2.8.5", + "dockerode": "^4.0.2", "dotenv": "^16.4.4", "express": "^4.18.2", "express-session": "^1.18.0", @@ -27,7 +29,10 @@ "socket.io-client": "^4.7.2" }, "devDependencies": { + "@types/node": "^22.8.4", + "cross-env": "^7.0.3", "jest": "^29.7.0", + "jest-mock": "^29.7.0", "nodemon": "^3.0.1", "supertest": "^6.3.4" }, @@ -36,106 +41,61 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -150,62 +110,43 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -213,87 +154,40 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -303,148 +197,68 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -457,6 +271,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -469,6 +284,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -481,6 +297,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -488,11 +305,44 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -505,6 +355,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -513,12 +364,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -532,6 +384,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -544,6 +397,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -556,6 +410,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -568,6 +423,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -580,6 +436,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -592,6 +449,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -599,11 +457,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -615,12 +490,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -630,88 +506,72 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "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", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -728,6 +588,7 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -737,6 +598,7 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -754,6 +616,7 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -801,6 +664,7 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -816,6 +680,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -829,6 +694,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" }, @@ -841,6 +707,7 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -858,6 +725,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -873,6 +741,7 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -911,50 +780,12 @@ } } }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -967,6 +798,7 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -981,6 +813,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -996,6 +829,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -1011,6 +845,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -1037,6 +872,7 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -1050,48 +886,53 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1101,6 +942,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -1116,46 +958,11 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", - "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -1164,13 +971,15 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -1180,20 +989,23 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, "node_modules/@types/babel__core": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", - "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -1203,10 +1015,11 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } @@ -1216,53 +1029,109 @@ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" }, "node_modules/@types/cors": { - "version": "2.8.15", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz", - "integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -1272,42 +1141,87 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, "node_modules/@types/node": { - "version": "20.8.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", - "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "license": "MIT", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", - "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", "dependencies": { "@types/webidl-conversions": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -1316,7 +1230,8 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", @@ -1326,12 +1241,14 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -1344,6 +1261,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { "debug": "4" }, @@ -1351,32 +1269,12 @@ "node": ">= 6.0.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -1391,6 +1289,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -1399,6 +1298,11 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "color-convert": "^2.0.1" }, @@ -1414,6 +1318,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1425,17 +1330,21 @@ "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -1449,6 +1358,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -1456,19 +1366,31 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "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/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -1483,6 +1405,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -1504,6 +1427,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -1520,6 +1444,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -1536,6 +1461,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1545,6 +1471,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -1556,23 +1483,27 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -1583,6 +1514,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, + "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -1597,12 +1529,34 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "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/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -1620,6 +1574,7 @@ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" @@ -1628,19 +1583,44 @@ "node": ">= 10.0.0" } }, + "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.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "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/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -1660,10 +1640,26 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1673,6 +1669,11 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "fill-range": "^7.1.1" }, @@ -1681,9 +1682,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -1699,11 +1700,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -1717,27 +1719,64 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } }, "node_modules/bson": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz", - "integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", + "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", + "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } }, + "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-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "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==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "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/busboy": { "version": "1.6.0", @@ -1754,6 +1793,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1762,6 +1802,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1781,6 +1822,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1790,14 +1832,15 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001559", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", - "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", + "version": "1.0.30001679", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz", + "integrity": "sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==", "dev": true, "funding": [ { @@ -1812,12 +1855,18 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1829,6 +1878,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, +<<<<<<< HEAD "node_modules/chalk/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1848,26 +1898,24 @@ "node": ">=8" } }, +======= +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1880,6 +1928,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -1888,6 +1939,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } @@ -1902,21 +1954,24 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1931,6 +1986,7 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -1940,12 +1996,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "color-name": "~1.1.4" }, @@ -1956,12 +2018,19 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", +<<<<<<< HEAD "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" +======= + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" +>>>>>>> mgrit/dev-it3-it4-PFEA2024 }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "bin": { "color-support": "bin.js" } @@ -1971,6 +2040,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1983,6 +2053,7 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -1990,7 +2061,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -1999,6 +2071,7 @@ "engines": [ "node >= 0.8" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -2010,6 +2083,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2023,12 +2097,14 @@ "node_modules/concat-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/concat-stream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -2036,12 +2112,14 @@ "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -2053,6 +2131,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2061,12 +2140,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2074,23 +2155,27 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -2099,11 +2184,26 @@ "node": ">= 0.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", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -2120,10 +2220,36 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cross-spawn": { + "node_modules/cross-env": { "version": "7.0.3", +<<<<<<< HEAD "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", +======= + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2134,18 +2260,28 @@ } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, + "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -2160,6 +2296,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2168,6 +2305,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2185,6 +2323,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2192,12 +2331,14 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2206,15 +2347,17 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -2224,6 +2367,7 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2233,6 +2377,7 @@ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, + "license": "ISC", "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -2243,14 +2388,45 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, + "license": "MIT", "engines": { "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/dotenv": { - "version": "16.4.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", - "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -2262,6 +2438,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -2269,19 +2446,22 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.574", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", - "integrity": "sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==", - "dev": true + "version": "1.5.55", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", + "integrity": "sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==", + "dev": true, + "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2292,27 +2472,39 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "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": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -2323,80 +2515,42 @@ } }, "node_modules/engine.io-client": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", - "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", - "xmlhttprequest-ssl": "~2.0.0" + "xmlhttprequest-ssl": "~2.1.1" } }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-client/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -2405,6 +2559,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -2416,15 +2571,17 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2432,13 +2589,15 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2448,6 +2607,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -2460,6 +2620,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2469,6 +2630,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -2501,6 +2663,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -2513,16 +2676,17 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -2553,6 +2717,7 @@ "node": ">= 0.10.0" } }, +<<<<<<< HEAD "node_modules/express-session": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", @@ -2575,24 +2740,43 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" +======= + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" +>>>>>>> mgrit/dev-it3-it4-PFEA2024 }, "node_modules/fast-json-stable-stringify": { "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 + "dev": true, + "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } @@ -2601,6 +2785,11 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2612,6 +2801,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -2625,11 +2815,27 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -2647,10 +2853,11 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2665,6 +2872,7 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "dev": true, + "license": "MIT", "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", @@ -2679,6 +2887,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2687,10 +2896,12 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, +<<<<<<< HEAD "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2704,11 +2915,19 @@ "engines": { "node": ">=10" } +======= + "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" +>>>>>>> mgrit/dev-it3-it4-PFEA2024 }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -2720,6 +2939,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -2727,10 +2947,17 @@ "node": ">=8" } }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2738,6 +2965,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2750,6 +2978,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2758,6 +2987,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -2778,6 +3009,7 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2787,6 +3019,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2795,6 +3028,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -2814,6 +3048,7 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -2823,6 +3058,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -2834,6 +3070,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2854,6 +3092,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2866,6 +3105,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2874,6 +3114,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2884,21 +3125,29 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", +<<<<<<< HEAD "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" +======= + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" +>>>>>>> mgrit/dev-it3-it4-PFEA2024 }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -2910,6 +3159,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2921,6 +3171,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2931,12 +3182,14 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2949,6 +3202,7 @@ "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2957,12 +3211,14 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -2978,6 +3234,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -2986,32 +3243,12 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -3020,6 +3257,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -3027,17 +3265,39 @@ "node": ">=0.10.0" } }, + "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-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, + "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -3057,6 +3317,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3065,6 +3326,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3073,12 +3336,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -3087,13 +3352,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -3102,12 +3369,16 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3132,6 +3403,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3140,6 +3412,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -3149,6 +3422,7 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3158,6 +3432,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -3169,6 +3444,11 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "engines": { "node": ">=0.12.0" } @@ -3178,6 +3458,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -3199,31 +3480,40 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", +<<<<<<< HEAD "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" +======= + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" +>>>>>>> mgrit/dev-it3-it4-PFEA2024 }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -3236,6 +3526,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -3245,25 +3536,20 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { + "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/istanbul-lib-source-maps": { @@ -3271,6 +3557,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -3280,34 +3567,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -3321,6 +3586,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3347,6 +3613,7 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -3356,26 +3623,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-circus": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3402,26 +3655,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -3455,6 +3694,7 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -3500,6 +3740,7 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -3515,6 +3756,7 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, + "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, @@ -3527,6 +3769,7 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -3543,6 +3786,7 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -3560,6 +3804,7 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3569,6 +3814,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -3589,50 +3835,12 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jest-leak-detector": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -3646,6 +3854,7 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -3661,6 +3870,7 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -3681,6 +3891,7 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3695,6 +3906,7 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -3712,6 +3924,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3721,6 +3934,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -3741,6 +3955,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, + "license": "MIT", "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -3754,6 +3969,7 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -3781,75 +3997,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -3883,6 +4036,7 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -3914,6 +4068,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3931,6 +4086,7 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -3948,6 +4104,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -3960,6 +4117,7 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -3974,17 +4132,51 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3994,22 +4186,24 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify": { "version": "1.1.1", @@ -4038,6 +4232,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -4068,6 +4263,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -4085,15 +4281,11 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/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/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -4104,6 +4296,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" @@ -4122,6 +4315,7 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4131,6 +4325,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4139,13 +4334,15 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -4156,69 +4353,85 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "semver": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } @@ -4227,6 +4440,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4234,12 +4448,14 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -4248,12 +4464,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4262,6 +4480,11 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4274,6 +4497,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -4285,6 +4509,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4293,6 +4518,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -4305,6 +4531,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4313,6 +4540,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4324,6 +4552,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4332,6 +4561,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -4340,6 +4570,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -4352,6 +4583,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -4359,24 +4591,38 @@ "node": ">=8" } }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, "bin": { "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" } }, + "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/mongodb": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", - "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", + "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", + "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.0", - "bson": "^6.2.0", + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -4416,23 +4662,26 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", - "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^13.0.0" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/multer": { "version": "1.4.5-lts.1", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.0.0", @@ -4446,27 +4695,25 @@ "node": ">= 6.0.0" } }, - "node_modules/multer/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "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/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4474,12 +4721,14 @@ "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4498,17 +4747,20 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -4518,30 +4770,34 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", - "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", - "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", @@ -4562,26 +4818,34 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } }, "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -4589,7 +4853,7 @@ "nopt": "bin/nopt.js" }, "engines": { - "node": "*" + "node": ">=6" } }, "node_modules/normalize-path": { @@ -4597,6 +4861,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4606,6 +4871,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -4617,6 +4883,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -4633,14 +4901,16 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4660,6 +4930,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -4679,6 +4950,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -4688,6 +4960,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -4722,15 +4995,16 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4741,6 +5015,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -4748,11 +5023,28 @@ "node": ">=8" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4762,6 +5054,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -4779,6 +5072,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4897,6 +5191,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4905,6 +5200,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4913,6 +5209,11 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "engines": { "node": ">=8" } @@ -4921,12 +5222,14 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" }, "node_modules/pause": { "version": "0.0.1", @@ -4934,15 +5237,21 @@ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "engines": { "node": ">=8.6" }, @@ -4955,6 +5264,7 @@ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -4964,6 +5274,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -4976,6 +5287,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -4990,6 +5302,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5000,13 +5313,15 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -5019,6 +5334,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -5031,20 +5347,32 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true + "dev": true, + "license": "MIT" + }, + "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", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -5055,12 +5383,14 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -5083,6 +5413,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5091,6 +5422,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -5102,15 +5434,17 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" }, "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", @@ -5125,6 +5459,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -5137,6 +5472,7 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5146,6 +5482,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -5163,6 +5500,7 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -5175,6 +5513,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5184,6 +5523,7 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -5192,6 +5532,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -5219,20 +5561,20 @@ "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", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5244,6 +5586,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -5263,23 +5606,35 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/send/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/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -5293,12 +5648,14 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -5314,12 +5671,18 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5331,6 +5694,11 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "engines": { "node": ">=8" } @@ -5339,6 +5707,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -5355,13 +5724,15 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -5373,27 +5744,30 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -5405,71 +5779,32 @@ "version": "2.5.5", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/socket.io-client": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", - "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.5.2", + "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-client/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -5478,76 +5813,72 @@ "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" } }, + "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 + "dev": true, + "license": "BSD-3-Clause" + }, + "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", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -5559,6 +5890,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5575,6 +5907,7 @@ "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" } @@ -5584,6 +5917,7 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -5596,6 +5930,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5609,6 +5944,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5621,6 +5957,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5630,6 +5967,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5639,6 +5977,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5650,7 +5989,9 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -5667,28 +6008,12 @@ "node": ">=6.4.0 <13 || >=14" } }, - "node_modules/superagent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -5696,17 +6021,12 @@ "node": ">=4.0.0" } }, - "node_modules/superagent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/supertest": { "version": "6.3.4", "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", "dev": true, + "license": "MIT", "dependencies": { "methods": "^1.1.2", "superagent": "^8.1.2" @@ -5716,15 +6036,16 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -5732,6 +6053,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5743,6 +6065,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -5755,11 +6078,64 @@ "node": ">=10" } }, + "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-fs/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/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/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -5784,21 +6160,18 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, - "engines": { - "node": ">=4" - } + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", +<<<<<<< HEAD +======= + "dev": true, + "license": "MIT", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "is-number": "^7.0.0" }, @@ -5810,18 +6183,17 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -5830,6 +6202,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "license": "MIT", "dependencies": { "punycode": "^2.3.0" }, @@ -5837,11 +6210,18 @@ "node": ">=14" } }, + "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-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -5851,6 +6231,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -5862,6 +6243,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -5873,7 +6255,8 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" }, "node_modules/uid-safe": { "version": "2.1.5", @@ -5895,12 +6278,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", @@ -5914,14 +6299,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -5937,9 +6323,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -5951,21 +6338,24 @@ "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==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -5979,6 +6369,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5988,6 +6379,7 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } @@ -5996,6 +6388,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" } @@ -6004,6 +6397,7 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "license": "MIT", "dependencies": { "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" @@ -6016,6 +6410,11 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", +<<<<<<< HEAD +======= + "dev": true, + "license": "ISC", +>>>>>>> mgrit/dev-it3-it4-PFEA2024 "dependencies": { "isexe": "^2.0.0" }, @@ -6030,6 +6429,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -6039,6 +6439,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6054,13 +6455,15 @@ "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -6073,6 +6476,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -6090,9 +6494,9 @@ } }, "node_modules/xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", "engines": { "node": ">=0.4.0" } @@ -6101,6 +6505,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", "engines": { "node": ">=0.4" } @@ -6110,14 +6515,17 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "2.5.1", @@ -6135,6 +6543,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -6153,6 +6562,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -6162,6 +6572,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/server/package.json b/server/package.json index c5015d0..c52a284 100644 --- a/server/package.json +++ b/server/package.json @@ -6,22 +6,25 @@ "scripts": { "build": "webpack --config webpack.config.js", "start": "node app.js", - "dev": "nodemon app.js", - "test": "jest", - "postinstall": "patch-package" + "postinstall": "patch-package", + "dev": "cross-env NODE_ENV=development nodemon app.js", + "test": "jest --colors" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { + "@types/express": "^5.0.0", "bcrypt": "^5.1.1", "cors": "^2.8.5", + "dockerode": "^4.0.2", "dotenv": "^16.4.4", "express": "^4.18.2", "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", + "net": "^1.0.2", "nodemailer": "^6.9.9", "passport": "^0.7.0", "passport-oauth2": "^1.8.0", @@ -31,7 +34,10 @@ "socket.io-client": "^4.7.2" }, "devDependencies": { + "@types/node": "^22.8.4", + "cross-env": "^7.0.3", "jest": "^29.7.0", + "jest-mock": "^29.7.0", "nodemon": "^3.0.1", "supertest": "^6.3.4" }, diff --git a/server/roomsProviders/base-provider.js b/server/roomsProviders/base-provider.js new file mode 100644 index 0000000..81899b7 --- /dev/null +++ b/server/roomsProviders/base-provider.js @@ -0,0 +1,75 @@ +/** + * @template T + * @typedef {import('../../types/room').RoomInfo} RoomInfo + * @typedef {import('../../types/room').RoomOptions} RoomOptions + * @typedef {import('../../types/room').BaseProviderConfig} BaseProviderConfig + */ + +const MIN_NB_SECONDS_BEFORE_CLEANUP = process.env.MIN_NB_SECONDS_BEFORE_CLEANUP || 60 + +class BaseRoomProvider { + constructor(config = {}, roomRepository) { + this.config = config; + this.roomRepository = roomRepository; + + this.quiz_docker_image = process.env.QUIZROOM_IMAGE || "evaluetonsavoir-quizroom"; + this.quiz_docker_port = process.env.QUIZROOM_PORT || 4500; + this.quiz_expose_port = process.env.QUIZROOM_EXPOSE_PORT || false; + } + + async createRoom(roomId, options) { + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async deleteRoom(roomId) { + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async getRoomStatus(roomId) { + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async listRooms() { + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async cleanup() { + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async syncInstantiatedRooms(){ + throw new Error("Fonction non-implantée - classe abstraite"); + } + + async updateRoomsInfo() { + const rooms = await this.roomRepository.getAll(); + for(var room of rooms){ + const url = `${room.host}/health`; + try { + const response = await fetch(url); + + if (!response.ok) { + room.mustBeCleaned = true; + await this.roomRepository.update(room); + continue; + } + + const json = await response.json(); + room.nbStudents = json.connections; + room.mustBeCleaned = room.nbStudents === 0 && json.uptime >MIN_NB_SECONDS_BEFORE_CLEANUP; + + await this.roomRepository.update(room); + } catch (error) { + room.mustBeCleaned = true; + await this.roomRepository.update(room); + } + } + } + + async getRoomInfo(roomId) { + const info = await this.roomRepository.get(roomId); + return info; + } +} + +module.exports = BaseRoomProvider; diff --git a/server/roomsProviders/cluster-provider.js b/server/roomsProviders/cluster-provider.js new file mode 100644 index 0000000..53bd93c --- /dev/null +++ b/server/roomsProviders/cluster-provider.js @@ -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; diff --git a/server/roomsProviders/docker-provider.js b/server/roomsProviders/docker-provider.js new file mode 100644 index 0000000..1da5c3b --- /dev/null +++ b/server/roomsProviders/docker-provider.js @@ -0,0 +1,275 @@ +const Docker = require("dockerode"); +const { Room } = require("../models/room.js"); +const BaseRoomProvider = require("./base-provider.js"); + +class DockerRoomProvider extends BaseRoomProvider { + constructor(config, roomRepository) { + super(config, roomRepository); + const dockerSocket = process.env.DOCKER_SOCKET || "/var/run/docker.sock"; + + this.docker = new Docker({ socketPath: dockerSocket }); + this.docker_network = process.env.QUIZ_NETWORK_NAME || 'evaluetonsavoir_quiz_network'; + } + + async syncInstantiatedRooms() { + let containers = await this.docker.listContainers(); + containers = containers.filter(container => container.Image === this.quiz_docker_image); + + const containerIds = new Set(containers.map(container => container.Id)); + + for (let container of containers) { + const container_name = container.Names[0].slice(1); + if (!container_name.startsWith("room_")) { + console.warn(`Le conteneur ${container_name} ne suit pas la convention de nommage, il sera supprimé.`); + const curContainer = this.docker.getContainer(container.Id); + await curContainer.stop(); + await curContainer.remove(); + containerIds.delete(container.Id); + console.warn(`Le conteneur ${container_name} a été supprimé.`); + } + else { + console.warn(`Conteneur orphelin trouvé : ${container_name}`); + const roomId = container_name.slice(5); + const room = await this.roomRepository.get(roomId); + + if (!room) { + console.warn(`Le conteneur n'est pas dans notre base de données.`); + const containerInfo = await this.docker.getContainer(container.Id).inspect(); + const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress; + const host = `${containerIP}:4500`; + console.warn(`Création de la salle ${roomId} dans notre base de donnée - hôte : ${host}`); + return await this.roomRepository.create(new Room(roomId, container_name, host)); + } + + console.warn(`La salle ${roomId} est déjà dans notre base de données.`); + } + } + } + + async checkAndPullImage(imageName) { + try { + const images = await this.docker.listImages({ all: true }); + //console.log('Images disponibles:', images.map(img => ({ + // RepoTags: img.RepoTags || [], + // Id: img.Id + //}))); + + const imageExists = images.some(img => { + const tags = img.RepoTags || []; + return tags.includes(imageName) || + tags.includes(`${imageName}:latest`) || + img.Id.includes(imageName); + }); + + if (!imageExists) { + console.log(`L'image ${imageName} n'a pas été trouvée localement, tentative de téléchargement...`); + try { + await this.docker.pull(imageName); + console.log(`L'image ${imageName} a été téléchargée avec succès`); + } catch (pullError) { + const localImages = await this.docker.listImages({ all: true }); + const foundLocally = localImages.some(img => + (img.RepoTags || []).includes(imageName) || + (img.RepoTags || []).includes(`${imageName}:latest`) + ); + + if (!foundLocally) { + throw new Error(`Impossible de trouver ou de télécharger l'image ${imageName}: ${pullError.message}`); + } else { + console.log(`L'image ${imageName} a été trouvée localement après vérification supplémentaire`); + } + } + } else { + console.log(`L'image ${imageName} a été trouvée localement`); + } + } catch (error) { + throw new Error(`Une erreur est survenue lors de la vérification/téléchargement de l'image ${imageName}: ${error.message}`); + } + } + + async createRoom(roomId, options) { + const container_name = `room_${roomId}`; + + try { + await this.checkAndPullImage(this.quiz_docker_image); + const containerConfig = { + Image: this.quiz_docker_image, + name: container_name, + HostConfig: { + NetworkMode: this.docker_network, + RestartPolicy: { + Name: 'unless-stopped' + }, + Binds: [ + '/var/run/docker.sock:/var/run/docker.sock' + ] + }, + Env: [ + `ROOM_ID=${roomId}`, + `PORT=${this.quiz_docker_port}`, + ...(options.env || []) + ] + }; + + if (this.quiz_expose_port) { + containerConfig.ExposedPorts = { + [`${this.quiz_docker_port}/tcp`]: {} + }; + containerConfig.HostConfig.PortBindings = { + [`${this.quiz_docker_port}/tcp`]: [{ HostPort: '' }] // Empty string for random port + }; + } + + const container = await this.docker.createContainer(containerConfig); + await container.start(); + + const containerInfo = await container.inspect(); + const networkInfo = containerInfo.NetworkSettings.Networks[this.docker_network]; + + if (!networkInfo) { + throw new Error(`Le conteneur n'as pu se connecter au réseau: ${this.docker_network}`); + } + + const containerIP = networkInfo.IPAddress; + const host = `http://${containerIP}:${this.quiz_docker_port}`; + + + let health = false; + let attempts = 0; + const maxAttempts = 15; + + while (!health && attempts < maxAttempts) { + try { + const response = await fetch(`${host}/health`, { + timeout: 1000 + }); + + if (response.ok) { + health = true; + console.log(`Le conteneur ${container_name} est tombé actif en ${attempts + 1} tentatives`); + } else { + throw new Error(`Health check failed with status ${response.status}`); + } + } catch (error) { + attempts++; + console.log(`Attente du conteneur: ${container_name} (tentative ${attempts}/${maxAttempts})`); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + if (!health) { + console.error(`Container ${container_name} failed health check after ${maxAttempts} attempts`); + await container.stop(); + await container.remove(); + throw new Error(`Room ${roomId} did not respond within acceptable timeout`); + } + + return await this.roomRepository.create(new Room(roomId, container_name, host, 0)); + } catch (error) { + console.error(`Échec de la création de la salle ${roomId}:`, error); + throw error; + } + } + + async deleteRoom(roomId) { + const container_name = `room_${roomId}`; + await this.roomRepository.delete(roomId); + + try { + const container = this.docker.getContainer(container_name); + const containerInfo = await container.inspect(); + + if (containerInfo) { + await container.stop(); + await container.remove(); + console.log(`Le conteneur pour la salle ${roomId} a été arrêté et supprimé.`); + } + } catch (error) { + if (error.statusCode === 404) { + console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, la salle sera supprimée de la base de données.`); + } else { + console.error(`Erreur pour la salle ${roomId}:`, error); + throw new Error("La salle :${roomId} n'as pas pu être supprimée."); + } + } + + console.log(`La salle ${roomId} a été supprimée.`); + } + + async getRoomStatus(roomId) { + const room = await this.roomRepository.get(roomId); + if (!room) return null; + + try { + const container = this.docker.getContainer(room.containerId || `room_${roomId}`); + 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) { + if (error.statusCode === 404) { + console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, il sera mis en état "terminé".`); + const terminatedRoomInfo = { + ...room, + status: "terminated", + containerStatus: { + Running: false, + StartedAt: room.containerStatus?.StartedAt || null, + FinishedAt: Date.now(), + }, + lastUpdate: Date.now(), + }; + + await this.roomRepository.update(terminatedRoomInfo); + return terminatedRoomInfo; + } else { + console.error(`Une érreur s'est produite lors de l'obtention de l'état de la salle ${roomId}:`, error); + return null; + } + } + } + + async listRooms() { + const rooms = await this.roomRepository.getAll(); + return rooms; + } + + async cleanup() { + const rooms = await this.roomRepository.getAll(); + for (let room of rooms) { + if (room.mustBeCleaned) { + try { + await this.deleteRoom(room.id); + } catch (error) { + console.error(`Érreur lors du néttoyage de la salle ${room.id}:`, error); + } + } + } + + let containers = await this.docker.listContainers(); + containers = containers.filter(container => container.Image === this.quiz_docker_image); + const roomIds = rooms.map(room => room.id); + + for (let container of containers) { + if (!roomIds.includes(container.Names[0].slice(6))) { + const curContainer = this.docker.getContainer(container.Id); + await curContainer.stop(); + await curContainer.remove(); + console.warn(`Conteneur orphelin ${container.Names[0]} supprimé.`); + } + } + } +} + +module.exports = DockerRoomProvider; diff --git a/server/routers/folders.js b/server/routers/folders.js index e64c18a..a7898ed 100644 --- a/server/routers/folders.js +++ b/server/routers/folders.js @@ -1,18 +1,22 @@ const express = require('express'); const router = express.Router(); const jwt = require('../middleware/jwtToken.js'); +const folders = require('../app.js').folders; -const foldersController = require('../controllers/folders.js') - -router.post("/create", jwt.authenticate, foldersController.create); -router.get("/getUserFolders", jwt.authenticate, foldersController.getUserFolders); -router.get("/getFolderContent/:folderId", jwt.authenticate, foldersController.getFolderContent); -router.delete("/delete/:folderId", jwt.authenticate, foldersController.delete); -router.put("/rename", jwt.authenticate, foldersController.rename); +router.post("/create", jwt.authenticate, folders.create); +router.get("/getUserFolders", jwt.authenticate, folders.getUserFolders); +router.get("/getFolderContent/:folderId", jwt.authenticate, folders.getFolderContent); +router.delete("/delete/:folderId", jwt.authenticate, folders.delete); +router.put("/rename", jwt.authenticate, folders.rename); //router.post("/duplicate", jwt.authenticate, foldersController.duplicate); -router.post("/duplicate", jwt.authenticate, foldersController.duplicate); +router.post("/duplicate", jwt.authenticate, folders.duplicate); -router.post("/copy/:folderId", jwt.authenticate, foldersController.copy); +router.post("/copy/:folderId", jwt.authenticate, folders.copy); -module.exports = router; \ No newline at end of file +module.exports = router; + +// export also folders (the controller) +module.exports.folders = folders; + +// Refer to folders using: const folders = require('../controllers/folders.js').folders; diff --git a/server/routers/health.js b/server/routers/health.js new file mode 100644 index 0000000..008452e --- /dev/null +++ b/server/routers/health.js @@ -0,0 +1,20 @@ +const express = require('express'); +const router = express.Router(); + +router.get('/', async (req, res) => { + try { + const dbStatus = await require('../config/db.js').getConnection() ? 'connected' : 'disconnected'; + res.json({ + status: 'healthy', + timestamp: new Date(), + db: dbStatus + }); + } catch (error) { + res.status(500).json({ + status: 'unhealthy', + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/images.js b/server/routers/images.js index d9b63b0..3723f45 100644 --- a/server/routers/images.js +++ b/server/routers/images.js @@ -1,15 +1,15 @@ const express = require('express'); const router = express.Router(); +const images = require('../app.js').images; const jwt = require('../middleware/jwtToken.js'); -const imagesController = require('../controllers/images.js') // For getting the image out of the form data const multer = require('multer'); const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); -router.post("/upload", jwt.authenticate, upload.single('image'), imagesController.upload); -router.get("/get/:id", imagesController.get); +router.post("/upload", jwt.authenticate, upload.single('image'), images.upload); +router.get("/get/:id", images.get); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/server/routers/quiz.js b/server/routers/quiz.js index c0f7ea2..136e4c9 100644 --- a/server/routers/quiz.js +++ b/server/routers/quiz.js @@ -1,19 +1,22 @@ const express = require('express'); const router = express.Router(); - +const quizzes = require('../app.js').quizzes; const jwt = require('../middleware/jwtToken.js'); -const quizController = require('../controllers/quiz.js') -router.post("/create", jwt.authenticate, quizController.create); -router.get("/get/:quizId", jwt.authenticate, quizController.get); -router.delete("/delete/:quizId", jwt.authenticate, quizController.delete); -router.put("/update", jwt.authenticate, quizController.update); -router.put("/move", jwt.authenticate, quizController.move); +if (!quizzes) { + console.error("quizzes is not defined"); +} -router.post("/duplicate", jwt.authenticate, quizController.duplicate); -router.post("/copy/:quizId", jwt.authenticate, quizController.copy); -router.put("/Share", jwt.authenticate, quizController.Share); -router.get("/getShare/:quizId", jwt.authenticate, quizController.getShare); -router.post("/receiveShare", jwt.authenticate, quizController.receiveShare); +router.post("/create", jwt.authenticate, quizzes.create); +router.get("/get/:quizId", jwt.authenticate, quizzes.get); +router.delete("/delete/:quizId", jwt.authenticate, quizzes.delete); +router.put("/update", jwt.authenticate, quizzes.update); +router.put("/move", jwt.authenticate, quizzes.move); -module.exports = router; \ No newline at end of file +router.post("/duplicate", jwt.authenticate, quizzes.duplicate); +router.post("/copy/:quizId", jwt.authenticate, quizzes.copy); +router.put("/Share", jwt.authenticate, quizzes.share); +router.get("/getShare/:quizId", jwt.authenticate, quizzes.getShare); +router.post("/receiveShare", jwt.authenticate, quizzes.receiveShare); + +module.exports = router; diff --git a/server/routers/rooms.js b/server/routers/rooms.js new file mode 100644 index 0000000..b34b7d9 --- /dev/null +++ b/server/routers/rooms.js @@ -0,0 +1,54 @@ +const { Router } = require("express"); +const roomsController = require('../app.js').rooms; +const jwt = require('../middleware/jwtToken.js'); + +const router = Router(); + +router.get("/",jwt.authenticate, async (req, res)=> { + try { + const data = await roomsController.listRooms(); + res.json(data); + } catch (error) { + res.status(500).json({ error: "Échec de listage des salle" }); + } +}); + + +router.post("/",jwt.authenticate, async (req, res) => { + try { + const data = await roomsController.createRoom(); + res.json(data); + } catch (error) { + console.log(error); + res.status(500).json({ error: "Échec de la création de salle :" + error }); + } +}); + +router.put("/:id",jwt.authenticate, async (req, res) => { + try { + const data = await roomsController.updateRoom(req.params.id); + res.json(data); + } catch (error) { + res.status(500).json({ error: "Échec de la mise a jour de salle : "+error }); + } +}); + +router.delete("/:id",jwt.authenticate, async (req, res) => { + try { + const data = await roomsController.deleteRoom(req.params.id); + res.json(data); + } catch (error) { + res.status(500).json({ error: `Échec de suppression de la salle: `+error }); + } +}); + +router.get("/:id", async (req, res) => { + try { + const data = await roomsController.getRoomStatus(req.params.id); + res.json(data); + } catch (error) { + res.status(500).json({ error: "Impossible d'afficher les informations de la salle: " + error }); + } +}); + +module.exports = router; diff --git a/server/routers/users.js b/server/routers/users.js index 1ab9e4b..ed9047c 100644 --- a/server/routers/users.js +++ b/server/routers/users.js @@ -1,9 +1,10 @@ const express = require('express'); const router = express.Router(); - +const users = require('../app.js').users; const jwt = require('../middleware/jwtToken.js'); + const usersController = require('../controllers/users.js') -router.post("/delete-user", jwt.authenticate, usersController.delete); +router.post("/delete-user", jwt.authenticate, usersController); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/server/socket/socket.js b/server/socket/socket.js deleted file mode 100644 index 5efe1fe..0000000 --- a/server/socket/socket.js +++ /dev/null @@ -1,125 +0,0 @@ -const MAX_USERS_PER_ROOM = 60; -const MAX_TOTAL_CONNECTIONS = 2000; - -const setupWebsocket = (io) => { - 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 connexion 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"); - } - } else { - const roomName = generateRoomName(); - if (!io.sockets.adapter.rooms.get(roomName)) { - socket.join(roomName); - socket.emit("create-success", roomName); - } else { - socket.emit("create-failure"); - } - } - }); - - 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 }; diff --git a/test/stressTest/.dockerignore b/test/stressTest/.dockerignore new file mode 100644 index 0000000..1dcef2d --- /dev/null +++ b/test/stressTest/.dockerignore @@ -0,0 +1,2 @@ +node_modules +.env \ No newline at end of file diff --git a/test/stressTest/.env.example b/test/stressTest/.env.example new file mode 100644 index 0000000..f219d36 --- /dev/null +++ b/test/stressTest/.env.example @@ -0,0 +1,16 @@ +# Target url +BASE_URL=http://host.docker.internal +# Connection account +USER_EMAIL=admin@admin.com +USER_PASSWORD=admin + +# Stress test parameters +NUMBER_ROOMS=5 +USERS_PER_ROOM=60 + +# Optionnal +MAX_MESSAGES_ROUND=20 +CONVERSATION_INTERVAL=1000 +MESSAGE_RESPONSE_TIMEOUT=5000 +BATCH_DELAY=1000 +BATCH_SIZE=10 \ No newline at end of file diff --git a/test/stressTest/Dockerfile b/test/stressTest/Dockerfile new file mode 100644 index 0000000..a92942b --- /dev/null +++ b/test/stressTest/Dockerfile @@ -0,0 +1,13 @@ +FROM node:18 + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +VOLUME /app/output + +CMD ["node", "main.js"] \ No newline at end of file diff --git a/test/stressTest/README.md b/test/stressTest/README.md new file mode 100644 index 0000000..fbed7e1 --- /dev/null +++ b/test/stressTest/README.md @@ -0,0 +1,51 @@ +# Test de Charge - EvalueTonSavoir + +Ce conteneur permet d'exécuter des tests de charge sur l'application EvalueTonSavoir. + +## Prérequis + +- Docker +- Docker Compose + +## Configuration + +1. Créez un fichier `.env` à partir du modèle `.env.example`: + +```bash +copy .env.example .env +``` + +2. Modifiez les variables dans le fichier .env: + +```bash +# URL de l'application cible +BASE_URL=http://votre-url.com + +# Compte de connexion +USER_EMAIL=admin@admin.com +USER_PASSWORD=admin + +# Paramètres du test de charge +NUMBER_ROOMS=5 # Nombre de salles à créer +USERS_PER_ROOM=60 # Nombre d'utilisateurs par salle + +``` +#### Paramètres optionnels +Dans le fichier .env, vous pouvez aussi configurer: + +```bash +MAX_MESSAGES_ROUND=20 # Messages maximum par cycle +CONVERSATION_INTERVAL=1000 # Intervalle entre les messages (ms) +MESSAGE_RESPONSE_TIMEOUT=5000 # Timeout des réponses (ms) +BATCH_DELAY=1000 # Délai entre les lots (ms) +BATCH_SIZE=10 # Taille des lots d'utilisateurs +``` + +## Démarrage +Pour lancer le test de charge: + +Les résultats seront disponibles dans le dossier output/. + +```bash +docker compose up +``` diff --git a/test/stressTest/class/metrics.js b/test/stressTest/class/metrics.js new file mode 100644 index 0000000..a28bff0 --- /dev/null +++ b/test/stressTest/class/metrics.js @@ -0,0 +1,46 @@ +export class TestMetrics { + constructor() { + this.reset(); + } + + reset() { + this.roomsCreated = 0; + this.roomsFailed = 0; + this.usersConnected = 0; + this.userConnectionsFailed = 0; + this.messagesAttempted = 0; + this.messagesSent = 0; + this.messagesReceived = 0; + this.errors = new Map(); + } + + logError(category, error) { + if (!this.errors.has(category)) { + this.errors.set(category, []); + } + this.errors.get(category).push(error); + } + + getSummary() { + return { + rooms: { + created: this.roomsCreated, + failed: this.roomsFailed, + total: this.roomsCreated + this.roomsFailed + }, + users: { + connected: this.usersConnected, + failed: this.userConnectionsFailed, + total: this.usersConnected + this.userConnectionsFailed + }, + messages: { + attempted: this.messagesAttempted, + sent: this.messagesSent, + received: this.messagesReceived + }, + errors: Object.fromEntries( + Array.from(this.errors.entries()).map(([k, v]) => [k, v.length]) + ) + }; + } +} \ No newline at end of file diff --git a/test/stressTest/class/roomParticipant.js b/test/stressTest/class/roomParticipant.js new file mode 100644 index 0000000..21cd1f6 --- /dev/null +++ b/test/stressTest/class/roomParticipant.js @@ -0,0 +1,83 @@ +import { io } from "socket.io-client"; + +export class RoomParticipant { + constructor(username, roomName) { + this.username = username; + this.roomName = roomName; + this.socket = null; + this.maxRetries = 3; + this.retryDelay = 1000; + } + + async connectToRoom(baseUrl) { + let retries = 0; + const maxRetries = 2; + const retryDelay = 2000; + + const cleanup = () => { + if (this.socket) { + this.socket.removeAllListeners(); + this.socket.disconnect(); + this.socket = null; + } + }; + + while (retries < maxRetries) { + try { + const socket = io(baseUrl, { + path: `/api/room/${this.roomName}/socket`, + transports: ['websocket'], + timeout: 8000, + reconnection: false, + forceNew: true + }); + + const result = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + cleanup(); + reject(new Error('Connection timeout')); + }, 8000); + + socket.on('connect', () => { + clearTimeout(timeout); + this.socket = socket; + this.onConnected(); // Add this line + resolve(socket); + }); + + socket.on('connect_error', (error) => { + clearTimeout(timeout); + cleanup(); + reject(new Error(`Connection error: ${error.message}`)); + }); + + socket.on('error', (error) => { + clearTimeout(timeout); + cleanup(); + reject(new Error(`Socket error: ${error.message}`)); + }); + }); + + return result; + + } catch (error) { + retries++; + if (retries === maxRetries) { + throw error; + } + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } + } + + onConnected() { + // To be implemented by child classes + } + + disconnect() { + if (this.socket) { + this.socket.disconnect(); + this.socket = null; + } + } +} \ No newline at end of file diff --git a/test/stressTest/class/student.js b/test/stressTest/class/student.js new file mode 100644 index 0000000..f1696ed --- /dev/null +++ b/test/stressTest/class/student.js @@ -0,0 +1,48 @@ +// student.js +import { RoomParticipant } from './roomParticipant.js'; + +export class Student extends RoomParticipant { + + nbrMessageReceived = 0; + + constructor(username, roomName) { + super(username, roomName); + } + + connectToRoom(baseUrl) { + return super.connectToRoom(baseUrl); + } + + onConnected() { + this.joinRoom(); + this.listenForTeacherMessage(); + } + + joinRoom() { + if (this.socket) { + this.socket.emit('join-room', { + enteredRoomName: this.roomName, + username: this.username + }); + } + } + + listenForTeacherMessage() { + if (this.socket) { + this.socket.on('message-sent-teacher', ({ message }) => { + this.nbrMessageReceived++; + this.respondToTeacher(message); + }); + } + } + + respondToTeacher(message) { + const reply = `${this.username} replying to: "${message}"`; + if (this.socket) { + this.socket.emit('message-from-student', { + roomName: this.roomName, + message: reply + }); + } + } +} \ No newline at end of file diff --git a/test/stressTest/class/teacher.js b/test/stressTest/class/teacher.js new file mode 100644 index 0000000..c6aaa9c --- /dev/null +++ b/test/stressTest/class/teacher.js @@ -0,0 +1,46 @@ +import { RoomParticipant } from './roomParticipant.js'; + +export class Teacher extends RoomParticipant { + + nbrMessageReceived = 0; + + constructor(username, roomName) { + super(username, roomName); + this.ready = false; + } + + connectToRoom(baseUrl) { + return super.connectToRoom(baseUrl); + } + + onConnected() { + this.createRoom(); + this.listenForStudentMessage(); + } + + createRoom() { + if (this.socket) { + this.socket.emit('create-room', this.roomName); + } + } + + broadcastMessage(message) { + if (this.socket) { + this.socket.emit('message-from-teacher', { + roomName: this.roomName, + message + }); + } else { + console.warn(`Teacher ${this.username} not ready to broadcast yet`); + } + } + + listenForStudentMessage() { + if (this.socket) { + this.socket.on('message-sent-student', ({ message }) => { + //console.log(`Teacher ${this.username} received: "${message}"`); + this.nbrMessageReceived++; + }); + } + } +} \ No newline at end of file diff --git a/test/stressTest/class/watcher.js b/test/stressTest/class/watcher.js new file mode 100644 index 0000000..e770e35 --- /dev/null +++ b/test/stressTest/class/watcher.js @@ -0,0 +1,72 @@ +import { RoomParticipant } from './roomParticipant.js'; + +export class Watcher extends RoomParticipant { + + roomRessourcesData = []; + checkRessourceInterval = null; + + constructor(username, roomName) { + super(username, roomName); + } + + connectToRoom(baseUrl) { + return super.connectToRoom(baseUrl); + } + + onConnected() { + this.startCheckingResources(); + } + + checkRessource() { + if (this.socket?.connected) { + try { + this.socket.emit("get-usage"); + this.socket.once("usage-data", (data) => { + const timestamp = Date.now(); + // Store each container's metrics separately with timestamp + data.forEach(containerStat => { + const existingData = this.roomRessourcesData.find(d => d.containerId === containerStat.containerId); + if (existingData) { + existingData.metrics.push({ + timestamp, + ...containerStat + }); + } else { + this.roomRessourcesData.push({ + containerId: containerStat.containerId, + containerName: containerStat.containerName, + metrics: [{ + timestamp, + ...containerStat + }] + }); + } + }); + }); + } catch (error) { + console.warn(`Error capturing metrics for room ${this.roomName}:`, error.message); + } + } + } + + startCheckingResources(intervalMs = 500) { + if (this.checkRessourceInterval) { + console.warn(`Resource checking is already running for room ${this.roomName}.`); + return; + } + + this.checkRessourceInterval = setInterval(() => this.checkRessource(), intervalMs); + } + + stopCheckingResources() { + if (this.checkRessourceInterval) { + clearInterval(this.checkRessourceInterval); + this.checkRessourceInterval = null; + } + } + + disconnect() { + this.stopCheckingResources(); + super.disconnect(); + } +} diff --git a/test/stressTest/docker-compose.yml b/test/stressTest/docker-compose.yml new file mode 100644 index 0000000..0254219 --- /dev/null +++ b/test/stressTest/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3' + +services: + + stress-test: + build: + context: . + dockerfile: Dockerfile + container_name: stress-test + #environment: + # - BASE_URL=http://host.docker.internal + # - USER_EMAIL=admin@admin.com + # - USER_PASSWORD=admin + # - NUMBER_ROOMS=5 + # - USERS_PER_ROOM=60 + # - MAX_MESSAGES_ROUND=20 + # - CONVERSATION_INTERVAL=1000 + # - MESSAGE_RESPONSE_TIMEOUT=5000 + # - BATCH_DELAY=1000 + # - BATCH_SIZE=10 + env_file: + - .env + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./output:/app/output \ No newline at end of file diff --git a/test/stressTest/main.js b/test/stressTest/main.js new file mode 100644 index 0000000..7373be0 --- /dev/null +++ b/test/stressTest/main.js @@ -0,0 +1,201 @@ +import { attemptLoginOrRegister, createRoomContainer } from './utility/apiServices.js'; +import { Student } from './class/student.js'; +import { Teacher } from './class/teacher.js'; +import { Watcher } from './class/watcher.js'; +import { TestMetrics } from './class/metrics.js'; +import dotenv from 'dotenv'; +import generateMetricsReport from './utility/metrics_generator.js'; + +dotenv.config(); + +const config = { + baseUrl: process.env.BASE_URL || 'http://host.docker.internal', + auth: { + username: process.env.USER_EMAIL || 'admin@admin.com', + password: process.env.USER_PASSWORD || 'admin' + }, + rooms: { + count: parseInt(process.env.NUMBER_ROOMS || '15'), + usersPerRoom: parseInt(process.env.USERS_PER_ROOM || '60'), + batchSize: parseInt(process.env.BATCH_SIZE || 5), + batchDelay: parseInt(process.env.BATCH_DELAY || 250) + }, + simulation: { + maxMessages: parseInt(process.env.MAX_MESSAGES_ROUND || '20'), + messageInterval: parseInt(process.env.CONVERSATION_INTERVAL || '1000'), + responseTimeout: parseInt(process.env.MESSAGE_RESPONSE_TIMEOUT || 5000) + } +}; + +const rooms = new Map(); +const metrics = new TestMetrics(); + +// Changes to setupRoom function +async function setupRoom(token, index) { + try { + const room = await createRoomContainer(config.baseUrl, token); + if (!room?.id) throw new Error('Room creation failed'); + metrics.roomsCreated++; + + const teacher = new Teacher(`teacher_${index}`, room.id); + // Only create watcher for first room (index 0) + const watcher = index === 0 ? new Watcher(`watcher_${index}`, room.id) : null; + + await Promise.all([ + teacher.connectToRoom(config.baseUrl) + .then(() => metrics.usersConnected++) + .catch(err => { + metrics.userConnectionsFailed++; + metrics.logError('teacherConnection', err); + console.warn(`Teacher ${index} connection failed:`, err.message); + }), + // Only connect watcher if it exists + ...(watcher ? [ + watcher.connectToRoom(config.baseUrl) + .then(() => metrics.usersConnected++) + .catch(err => { + metrics.userConnectionsFailed++; + metrics.logError('watcherConnection', err); + console.warn(`Watcher ${index} connection failed:`, err.message); + }) + ] : []) + ]); + + // Adjust number of students based on whether room has a watcher + const studentCount = watcher ? + config.rooms.usersPerRoom - 2 : // Room with watcher: subtract teacher and watcher + config.rooms.usersPerRoom - 1; // Rooms without watcher: subtract only teacher + + const students = Array.from({ length: studentCount }, + (_, i) => new Student(`student_${index}_${i}`, room.id)); + + rooms.set(room.id, { teacher, watcher, students }); + return room.id; + } catch (err) { + metrics.roomsFailed++; + metrics.logError('roomSetup', err); + console.warn(`Room ${index} setup failed:`, err.message); + return null; + } +} + +async function connectParticipants(roomId) { + const { students } = rooms.get(roomId); + const participants = [...students]; + + for (let i = 0; i < participants.length; i += config.rooms.batchSize) { + const batch = participants.slice(i, i + config.rooms.batchSize); + await Promise.all(batch.map(p => + Promise.race([ + p.connectToRoom(config.baseUrl).then(() => { + metrics.usersConnected++; + }), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 10000)) + ]).catch(err => { + metrics.userConnectionsFailed++; + metrics.logError('studentConnection', err); + console.warn(`Connection failed for ${p.username}:`, err.message); + }) + )); + await new Promise(resolve => setTimeout(resolve, config.rooms.batchDelay)); + } +} + +async function simulate() { + const simulations = Array.from(rooms.entries()).map(async ([roomId, { teacher, students }]) => { + const connectedStudents = students.filter(student => student.socket?.connected); + const expectedResponses = connectedStudents.length; + + for (let i = 0; i < config.simulation.maxMessages; i++) { + metrics.messagesAttempted++; + const initialMessages = teacher.nbrMessageReceived; + + try { + teacher.broadcastMessage(`Message ${i + 1} from ${teacher.username}`); + metrics.messagesSent++; + + await Promise.race([ + new Promise(resolve => { + const checkResponses = setInterval(() => { + const receivedResponses = teacher.nbrMessageReceived - initialMessages; + if (receivedResponses >= expectedResponses) { + metrics.messagesReceived += receivedResponses; + clearInterval(checkResponses); + resolve(); + } + }, 100); + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Response timeout')), config.simulation.responseTimeout) + ) + ]); + } catch (error) { + metrics.logError('messaging', error); + console.error(`Error in room ${roomId} message ${i + 1}:`, error); + } + + await new Promise(resolve => setTimeout(resolve, config.simulation.messageInterval)); + } + }); + + await Promise.all(simulations); + console.log('All room simulations completed'); +} + +async function generateReport() { + const watcherRoom = Array.from(rooms.entries()).find(([_, room]) => room.watcher); + if (!watcherRoom) { + throw new Error('No watcher found in any room'); + } + const data = { + [watcherRoom[0]]: watcherRoom[1].watcher.roomRessourcesData + }; + return generateMetricsReport(data, metrics); +} + +function cleanup() { + for (const { teacher, watcher, students } of rooms.values()) { + [teacher, watcher, ...students].forEach(p => p?.disconnect()); + } +} + +async function main() { + try { + const token = await attemptLoginOrRegister(config.baseUrl, config.auth.username, config.auth.password); + if (!token) throw new Error('Authentication failed'); + + console.log('Creating rooms...'); + const roomIds = await Promise.all( + Array.from({ length: config.rooms.count }, (_, i) => setupRoom(token, i)) + ); + + console.log('Connecting participants...'); + await Promise.all(roomIds.filter(Boolean).map(connectParticipants)); + + console.log('Retrieving baseline metrics...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + + console.log('Starting simulation across all rooms...'); + await simulate(); + + console.log('Simulation complete. Waiting for system stabilization...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + + console.log('Generating final report...'); + const folderName = await generateReport(); + console.log(`Metrics report generated in ${folderName.outputDir}`); + + console.log('All done!'); + } catch (error) { + metrics.logError('main', error); + console.error('Error:', error.message); + } finally { + cleanup(); + } +} + +['SIGINT', 'exit', 'uncaughtException', 'unhandledRejection'].forEach(event => { + process.on(event, cleanup); +}); + +main(); \ No newline at end of file diff --git a/test/stressTest/package-lock.json b/test/stressTest/package-lock.json new file mode 100644 index 0000000..ef4433e --- /dev/null +++ b/test/stressTest/package-lock.json @@ -0,0 +1,1230 @@ +{ + "name": "stresstest", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "stresstest", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.7.7", + "chart.js": "^3.9.1", + "chartjs-node-canvas": "^4.1.6", + "dockerode": "^4.0.2", + "dotenv": "^16.4.5", + "p-limit": "^6.1.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1" + } + }, + "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==" + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "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" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "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==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chart.js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" + }, + "node_modules/chartjs-node-canvas": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/chartjs-node-canvas/-/chartjs-node-canvas-4.1.6.tgz", + "integrity": "sha512-UQJbPWrvqB/FoLclGA9BaLQmZbzSYlujF4w8NZd6Xzb+sqgACBb2owDX6m7ifCXLjUW5Nz0Qx0qqrTtQkkSoYw==", + "dependencies": { + "canvas": "^2.8.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "chart.js": "^3.5.1" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.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/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^5.0.3", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "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==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "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==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "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==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.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==" + }, + "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/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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "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==", + "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/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "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==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "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==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/test/stressTest/package.json b/test/stressTest/package.json new file mode 100644 index 0000000..f228a34 --- /dev/null +++ b/test/stressTest/package.json @@ -0,0 +1,22 @@ +{ + "name": "stresstest", + "version": "1.0.0", + "description": "main.js", + "type": "module", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.7.7", + "chart.js": "^3.9.1", + "chartjs-node-canvas": "^4.1.6", + "dockerode": "^4.0.2", + "dotenv": "^16.4.5", + "p-limit": "^6.1.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1" + } +} diff --git a/test/stressTest/utility/apiServices.js b/test/stressTest/utility/apiServices.js new file mode 100644 index 0000000..d3e66ae --- /dev/null +++ b/test/stressTest/utility/apiServices.js @@ -0,0 +1,80 @@ +import axios from "axios"; + +// Logs in a user. +async function login(baseUrl, email, password) { + if (!email || !password) throw new Error("Email and password are required."); + + try { + const res = await axios.post(`${baseUrl}/api/user/login`, { email, password }, { + headers: { "Content-Type": "application/json" }, + }); + + if (res.status === 200 && res.data.token) { + console.log(`Login successful for ${email}`); + return res.data.token; + } + throw new Error(`Login failed. Status: ${res.status}`); + } catch (error) { + console.error(`Login error for ${email}:`, error.message); + throw error; + } +} + +// Registers a new user. +async function register(baseUrl, email, password) { + if (!email || !password) throw new Error("Email and password are required."); + + try { + const res = await axios.post(`${baseUrl}/api/user/register`, { email, password }, { + headers: { "Content-Type": "application/json" }, + }); + + if (res.status === 200) { + console.log(`Registration successful for ${email}`); + return res.data.message || "Registration completed successfully."; + } + throw new Error(`Registration failed. Status: ${res.status}`); + } catch (error) { + console.error(`Registration error for ${email}:`, error.message); + throw error; + } +} + +// Attempts to log in a user, or registers and logs in if the login fails. +export async function attemptLoginOrRegister(baseUrl, username, password) { + console.log(`Authenticating user with server : ${baseUrl}, username: ${username}, password: ${password}`); + try { + return await login(baseUrl, username, password); + } catch (loginError) { + console.error(`Login failed for ${username}:`, loginError.message); + } + + console.log(`Login failed for ${username}. Attempting registration...`); + try { + await register(baseUrl, username, password); + return await login(baseUrl, username, password); + } catch (registerError) { + console.error(`Registration and login failed for ${username}:`, registerError.message); + return null; + } +} + +// Creates a new room +export async function createRoomContainer(baseUrl, token) { + if (!token) throw new Error("Authorization token is required."); + + try { + const res = await axios.post(`${baseUrl}/api/room`, {}, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (res.status === 200) return res.data; + throw new Error(`Room creation failed. Status: ${res.status}`); + } catch (error) { + console.error("Room creation error:", error.message); + throw error; + } +} \ No newline at end of file diff --git a/test/stressTest/utility/metrics_generator.js b/test/stressTest/utility/metrics_generator.js new file mode 100644 index 0000000..022ff5b --- /dev/null +++ b/test/stressTest/utility/metrics_generator.js @@ -0,0 +1,253 @@ +import fs from 'fs'; +import path from 'path'; +import { ChartJSNodeCanvas } from 'chartjs-node-canvas'; + +async function saveMetricsSummary(metrics, baseOutputDir) { + const metricsData = metrics.getSummary(); + + // Save as JSON + fs.writeFileSync( + path.join(baseOutputDir, 'metrics-summary.json'), + JSON.stringify(metricsData, null, 2) + ); + + // Save as formatted text + const textSummary = ` +Load Test Summary +================ + +Rooms +----- +Created: ${metricsData.rooms.created} +Failed: ${metricsData.rooms.failed} +Total: ${metricsData.rooms.total} + +Users +----- +Connected: ${metricsData.users.connected} +Failed: ${metricsData.users.failed} +Total: ${metricsData.users.total} + +Messages +-------- +Attempted: ${metricsData.messages.attempted} +Sent: ${metricsData.messages.sent} +Received: ${metricsData.messages.received} + +Errors by Category +---------------- +${Object.entries(metricsData.errors) + .map(([category, count]) => `${category}: ${count}`) + .join('\n')} +`; + + fs.writeFileSync( + path.join(baseOutputDir, 'metrics-summary.txt'), + textSummary.trim() + ); +} + +// Common chart configurations +const CHART_CONFIG = { + width: 800, + height: 400, + chartStyles: { + memory: { + borderColor: 'blue', + backgroundColor: 'rgba(54, 162, 235, 0.2)' + }, + memoryPercent: { + borderColor: 'green', + backgroundColor: 'rgba(75, 192, 192, 0.2)' + }, + cpu: { + borderColor: 'red', + backgroundColor: 'rgba(255, 99, 132, 0.2)' + } + } +}; + +const createBaseChartConfig = (labels, dataset, xLabel, yLabel) => ({ + type: 'line', + data: { + labels, + datasets: [dataset] + }, + options: { + scales: { + x: { title: { display: true, text: xLabel }}, + y: { title: { display: true, text: yLabel }} + }, + plugins: { + legend: { display: true, position: 'top' } + } + } +}); + +function ensureDirectoryExists(directory) { + !fs.existsSync(directory) && fs.mkdirSync(directory, { recursive: true }); +} + +async function generateMetricsChart(chartJSNodeCanvas, data, style, label, timeLabels, metric, outputPath) { + const dataset = { + label, + data: data.map(m => m[metric] || 0), + ...CHART_CONFIG.chartStyles[style], + fill: true, + tension: 0.4 + }; + + const buffer = await chartJSNodeCanvas.renderToBuffer( + createBaseChartConfig(timeLabels, dataset, 'Time', label) + ); + + return fs.promises.writeFile(outputPath, buffer); +} + +async function generateContainerCharts(chartJSNodeCanvas, containerData, outputDir) { + const timeLabels = containerData.metrics.map(m => + new Date(m.timestamp).toLocaleTimeString() + ); + + const chartPromises = [ + generateMetricsChart( + chartJSNodeCanvas, + containerData.metrics, + 'memory', + `${containerData.containerName} Memory (MB)`, + timeLabels, + 'memoryUsedMB', + path.join(outputDir, 'memory-usage-mb.png') + ), + generateMetricsChart( + chartJSNodeCanvas, + containerData.metrics, + 'memoryPercent', + `${containerData.containerName} Memory %`, + timeLabels, + 'memoryUsedPercentage', + path.join(outputDir, 'memory-usage-percent.png') + ), + generateMetricsChart( + chartJSNodeCanvas, + containerData.metrics, + 'cpu', + `${containerData.containerName} CPU %`, + timeLabels, + 'cpuUsedPercentage', + path.join(outputDir, 'cpu-usage.png') + ) + ]; + + await Promise.all(chartPromises); +} + +async function processSummaryMetrics(containers, timeLabels) { + return timeLabels.map((_, timeIndex) => ({ + memoryUsedMB: containers.reduce((sum, container) => + sum + (container.metrics[timeIndex]?.memoryUsedMB || 0), 0), + memoryUsedPercentage: containers.reduce((sum, container) => + sum + (container.metrics[timeIndex]?.memoryUsedPercentage || 0), 0), + cpuUsedPercentage: containers.reduce((sum, container) => + sum + (container.metrics[timeIndex]?.cpuUsedPercentage || 0), 0) + })); +} + +export default async function generateMetricsReport(allRoomsData, testMetrics) { + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const baseOutputDir = `./output/${timestamp}`; + const dirs = [ + baseOutputDir, + path.join(baseOutputDir, 'all-containers'), + path.join(baseOutputDir, 'all-rooms') + ]; + + dirs.forEach(ensureDirectoryExists); + + if (testMetrics) { + await saveMetricsSummary(testMetrics, baseOutputDir); + } + + const chartJSNodeCanvas = new ChartJSNodeCanvas(CHART_CONFIG); + const allContainers = Object.values(allRoomsData).flat(); + const roomContainers = allContainers.filter(c => + c.containerName.startsWith('room_') + ); + + if (allContainers.length > 0) { + const timeLabels = allContainers[0].metrics.map(m => + new Date(m.timestamp).toLocaleTimeString() + ); + const summedMetrics = await processSummaryMetrics(allContainers, timeLabels); + await generateContainerSummaryCharts( + chartJSNodeCanvas, + summedMetrics, + timeLabels, + path.join(baseOutputDir, 'all-containers') + ); + } + + if (roomContainers.length > 0) { + const timeLabels = roomContainers[0].metrics.map(m => + new Date(m.timestamp).toLocaleTimeString() + ); + const summedMetrics = await processSummaryMetrics(roomContainers, timeLabels); + await generateContainerSummaryCharts( + chartJSNodeCanvas, + summedMetrics, + timeLabels, + path.join(baseOutputDir, 'all-rooms') + ); + } + + // Process individual containers + const containerPromises = Object.values(allRoomsData) + .flat() + .filter(container => !container.containerName.startsWith('room_')) + .map(async containerData => { + const containerDir = path.join(baseOutputDir, containerData.containerName); + ensureDirectoryExists(containerDir); + await generateContainerCharts(chartJSNodeCanvas, containerData, containerDir); + }); + + await Promise.all(containerPromises); + + return { outputDir: baseOutputDir }; + } catch (error) { + console.error('Error generating metrics report:', error); + throw error; + } +} + +async function generateContainerSummaryCharts(chartJSNodeCanvas, metrics, timeLabels, outputDir) { + await Promise.all([ + generateMetricsChart( + chartJSNodeCanvas, + metrics, + 'memory', + 'Total Memory (MB)', + timeLabels, + 'memoryUsedMB', + path.join(outputDir, 'total-memory-usage-mb.png') + ), + generateMetricsChart( + chartJSNodeCanvas, + metrics, + 'memoryPercent', + 'Total Memory %', + timeLabels, + 'memoryUsedPercentage', + path.join(outputDir, 'total-memory-usage-percent.png') + ), + generateMetricsChart( + chartJSNodeCanvas, + metrics, + 'cpu', + 'Total CPU %', + timeLabels, + 'cpuUsedPercentage', + path.join(outputDir, 'total-cpu-usage.png') + ) + ]); +} \ No newline at end of file diff --git a/types/room.ts b/types/room.ts new file mode 100644 index 0000000..bd29953 --- /dev/null +++ b/types/room.ts @@ -0,0 +1,69 @@ +export interface RoomInfo { + roomId: string; + status: RoomStatus; + createdAt: number; + lastUpdate?: number; + provider: ProviderType; + error?: string; + } + + export interface RoomOptions { + roomId?: string; + maxUsers?: number; + timeout?: number; + [key: string]: any; + } + + export type RoomStatus = 'creating' | 'running' | 'error' | 'terminated'; + export type ProviderType = 'cluster' | 'docker' | 'kubernetes'; + + // Provider-specific room information + export interface ClusterRoomInfo extends RoomInfo { + workerId: number; + pid: number; + } + + export interface DockerRoomInfo extends RoomInfo { + containerId: string; + containerIp: string; + containerStatus?: { + Running: boolean; + StartedAt: string; + FinishedAt: string; + }; + } + + export interface KubernetesRoomInfo extends RoomInfo { + deploymentName: string; + namespace: string; + deploymentStatus?: { + availableReplicas: number; + readyReplicas: number; + replicas: number; + }; + } + + // Provider configuration interfaces + export interface BaseProviderConfig { + redisUrl?: string; + } + + export interface ClusterProviderConfig extends BaseProviderConfig { + maxWorkersPerRoom?: number; + } + + export interface DockerProviderConfig extends BaseProviderConfig { + dockerConfig?: any; + networkName?: string; + containerImage?: string; + } + + export interface KubernetesProviderConfig extends BaseProviderConfig { + namespace?: string; + kubeConfig?: any; + } + + export type ProviderConfig = + | ClusterProviderConfig + | DockerProviderConfig + | KubernetesProviderConfig; \ No newline at end of file diff --git a/types/valkey.ts b/types/valkey.ts new file mode 100644 index 0000000..a51d288 --- /dev/null +++ b/types/valkey.ts @@ -0,0 +1,9 @@ +export interface ValkeyBasicConfig{ + host:string, + port:number +} + +const valkeyDefaults : ValkeyBasicConfig = { + host:'localhost', + port:6379 +} \ No newline at end of file