From 576e48562b08df3c97da103ef05f41da4dbee7d3 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 24 Oct 2025 01:50:45 +0300 Subject: [PATCH 001/210] Initial Setup using starter package --- .dockerignore | 18 + .editorconfig | 9 + .eslintignore | 1 + .eslintrc | 18 + .gitignore | 141 + .huskyrc | 5 + .lintstagedrc.json | 5 + .prettierrc | 8 + .swcrc | 38 + .vscode/launch.json | 35 + .vscode/settings.json | 6 + Dockerfile.dev | 19 + Dockerfile.prod | 19 + Makefile | 46 + docker-compose.yml | 51 + ecosystem.config.js | 57 + jest.config.js | 12 + nginx.conf | 40 + nodemon.json | 12 + package-lock.json | 11326 ++++++++++++++++ package.json | 84 + src/app.ts | 81 + src/config/index.ts | 5 + src/controllers/auth.controller.ts | 44 + src/controllers/users.controller.ts | 63 + src/dtos/users.dto.ts | 20 + src/exceptions/HttpException.ts | 10 + src/http/auth.http | 27 + src/http/users.http | 34 + src/interfaces/auth.interface.ts | 15 + src/interfaces/routes.interface.ts | 6 + src/interfaces/users.interface.ts | 5 + src/middlewares/auth.middleware.ts | 39 + src/middlewares/error.middleware.ts | 15 + src/middlewares/validation.middleware.ts | 27 + .../20210314081925_initial/migration.sql | 9 + src/prisma/migrations/migration_lock.toml | 3 + src/prisma/schema.prisma | 17 + src/routes/auth.route.ts | 22 + src/routes/users.route.ts | 23 + src/server.ts | 10 + src/services/auth.service.ts | 56 + src/services/users.service.ts | 49 + src/test/auth.test.ts | 81 + src/test/index.test.ts | 18 + src/test/users.test.ts | 134 + src/utils/logger.ts | 65 + src/utils/validateEnv.ts | 8 + swagger.yaml | 123 + tsconfig.json | 38 + 50 files changed, 12997 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .huskyrc create mode 100644 .lintstagedrc.json create mode 100644 .prettierrc create mode 100644 .swcrc create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Dockerfile.dev create mode 100644 Dockerfile.prod create mode 100644 Makefile create mode 100644 docker-compose.yml create mode 100644 ecosystem.config.js create mode 100644 jest.config.js create mode 100644 nginx.conf create mode 100644 nodemon.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/config/index.ts create mode 100644 src/controllers/auth.controller.ts create mode 100644 src/controllers/users.controller.ts create mode 100644 src/dtos/users.dto.ts create mode 100644 src/exceptions/HttpException.ts create mode 100644 src/http/auth.http create mode 100644 src/http/users.http create mode 100644 src/interfaces/auth.interface.ts create mode 100644 src/interfaces/routes.interface.ts create mode 100644 src/interfaces/users.interface.ts create mode 100644 src/middlewares/auth.middleware.ts create mode 100644 src/middlewares/error.middleware.ts create mode 100644 src/middlewares/validation.middleware.ts create mode 100644 src/prisma/migrations/20210314081925_initial/migration.sql create mode 100644 src/prisma/migrations/migration_lock.toml create mode 100644 src/prisma/schema.prisma create mode 100644 src/routes/auth.route.ts create mode 100644 src/routes/users.route.ts create mode 100644 src/server.ts create mode 100644 src/services/auth.service.ts create mode 100644 src/services/users.service.ts create mode 100644 src/test/auth.test.ts create mode 100644 src/test/index.test.ts create mode 100644 src/test/users.test.ts create mode 100644 src/utils/logger.ts create mode 100644 src/utils/validateEnv.ts create mode 100644 swagger.yaml create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0b2f116 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +# compiled output +.vscode +/node_modules + +# code formatter +.eslintrc +.eslintignore +.editorconfig +.huskyrc +.lintstagedrc.json +.prettierrc + +# test +jest.config.js + +# docker +Dockerfile +docker-compose.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3e22129 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/dist \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..206ab05 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": ["prettier", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/no-parameter-properties": 0, + "@typescript-eslint/interface-name-prefix": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-var-requires": "off" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ccb8df --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ diff --git a/.huskyrc b/.huskyrc new file mode 100644 index 0000000..4d077c8 --- /dev/null +++ b/.huskyrc @@ -0,0 +1,5 @@ +{ + "hooks": { + "pre-commit": "lint-staged" + } +} diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..d2fe776 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "*.ts": [ + "npm run lint" + ] +} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..93a5aaf --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 150, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/.swcrc b/.swcrc new file mode 100644 index 0000000..070d681 --- /dev/null +++ b/.swcrc @@ -0,0 +1,38 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false, + "dynamicImport": true, + "decorators": true + }, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "target": "es2017", + "externalHelpers": false, + "keepClassNames": true, + "loose": false, + "minify": { + "compress": false, + "mangle": false + }, + "baseUrl": "src", + "paths": { + "@/*": ["*"], + "@config": ["config"], + "@controllers/*": ["controllers/*"], + "@dtos/*": ["dtos/*"], + "@exceptions/*": ["exceptions/*"], + "@interfaces/*": ["interfaces/*"], + "@middlewares/*": ["middlewares/*"], + "@routes/*": ["routes/*"], + "@services/*": ["services/*"], + "@utils/*": ["utils/*"] + } + }, + "module": { + "type": "commonjs" + } +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..00ccfd7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node-terminal", + "request": "launch", + "name": "Dev typescript-express-starter", + "command": "npm run dev" + }, + { + "type": "node-terminal", + "request": "launch", + "name": "Start typescript-express-starter", + "command": "npm run start" + }, + { + "type": "node-terminal", + "request": "launch", + "name": "Test typescript-express-starter", + "command": "npm run test" + }, + { + "type": "node-terminal", + "request": "launch", + "name": "Lint typescript-express-starter", + "command": "npm run lint" + }, + { + "type": "node-terminal", + "request": "launch", + "name": "Lint:Fix typescript-express-starter", + "command": "npm run lint:fix" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..70abc46 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.formatOnSave": false +} diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..f0c9f87 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,19 @@ +# NodeJS Version 16 +FROM node:16.18-buster-slim + +# Copy Dir +COPY . ./app + +# Work to Dir +WORKDIR /app + +# Install Node Package +RUN npm install --legacy-peer-deps + +# Set Env +ENV NODE_ENV development + +EXPOSE 3000 + +# Cmd script +CMD ["npm", "run", "dev"] diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..ba23202 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,19 @@ +# NodeJS Version 16 +FROM node:16.18-buster-slim + +# Copy Dir +COPY . ./app + +# Work to Dir +WORKDIR /app + +# Install Node Package +RUN npm install --legacy-peer-deps + +# Set Env +ENV NODE_ENV production + +EXPOSE 3000 + +# Cmd script +CMD ["npm", "run", "start"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b8caca3 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +# app name should be overridden. +# ex) production-stage: make build APP_NAME= +# ex) development-stage: make build-dev APP_NAME= + +SHELL := /bin/bash + +APP_NAME = typescript-express +APP_NAME := $(APP_NAME) + +.PHONY: help start clean db test + +help: + @grep -E '^[1-9a-zA-Z_-]+:.*?## .*$$|(^#--)' $(MAKEFILE_LIST) \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m %-43s\033[0m %s\n", $$1, $$2}' \ + | sed -e 's/\[32m #-- /[33m/' + +#-- Docker +up: ## Up the container images + docker-compose up -d + +down: ## Down the container images + docker-compose down + +build: ## Build the container image - Production + docker build -t ${APP_NAME}\ + -f Dockerfile.prod . + +build-dev: ## Build the container image - Development + docker build -t ${APP_NAME}\ + -f Dockerfile.dev . + +run: ## Run the container image + docker run -d -it -p 3000:3000 ${APP_NAME} + +pause: ## Pause the containers + docker container rm -f ${APP_NAME} + +clean: ## Clean the images + docker rmi -f ${APP_NAME} + +remove: ## Remove the volumes + docker volume rm -f ${APP_NAME} + +#-- Database +db: ## Start the local database MySQL + docker-compose up -d mysql diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9ab30f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: "3.9" + +services: + proxy: + container_name: proxy + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + restart: "unless-stopped" + networks: + - backend + + server: + container_name: server + build: + context: ./ + dockerfile: Dockerfile.dev + ports: + - "3000:3000" + environment: + DATABASE_URL: mysql://root:password@localhost:3306/dev + volumes: + - ./:/app + - /app/node_modules + restart: "unless-stopped" + networks: + - backend + links: + - mysql + depends_on: + - mysql + + mysql: + container_name: mysql + image: mysql:5.7 + environment: + DATABASE_URL: mysql://root:password@localhost:3306/dev + ports: + - "3306:3306" + networks: + - backend + +networks: + backend: + driver: bridge + +volumes: + data: + driver: local diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..94cfa72 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,57 @@ +/** + * @description pm2 configuration file. + * @example + * production mode :: pm2 start ecosystem.config.js --only prod + * development mode :: pm2 start ecosystem.config.js --only dev + */ + module.exports = { + apps: [ + { + name: 'prod', // pm2 start App name + script: 'dist/server.js', + exec_mode: 'cluster', // 'cluster' or 'fork' + instance_var: 'INSTANCE_ID', // instance variable + instances: 2, // pm2 instance count + autorestart: true, // auto restart if process crash + watch: false, // files change automatic restart + ignore_watch: ['node_modules', 'logs'], // ignore files change + max_memory_restart: '1G', // restart if process use more than 1G memory + merge_logs: true, // if true, stdout and stderr will be merged and sent to pm2 log + output: './logs/access.log', // pm2 log file + error: './logs/error.log', // pm2 error log file + env: { // environment variable + PORT: 3000, + NODE_ENV: 'production', + }, + }, + { + name: 'dev', // pm2 start App name + script: 'ts-node', // ts-node + args: '-r tsconfig-paths/register --transpile-only src/server.ts', // ts-node args + exec_mode: 'cluster', // 'cluster' or 'fork' + instance_var: 'INSTANCE_ID', // instance variable + instances: 2, // pm2 instance count + autorestart: true, // auto restart if process crash + watch: false, // files change automatic restart + ignore_watch: ['node_modules', 'logs'], // ignore files change + max_memory_restart: '1G', // restart if process use more than 1G memory + merge_logs: true, // if true, stdout and stderr will be merged and sent to pm2 log + output: './logs/access.log', // pm2 log file + error: './logs/error.log', // pm2 error log file + env: { // environment variable + PORT: 3000, + NODE_ENV: 'development', + }, + }, + ], + deploy: { + production: { + user: 'user', + host: '0.0.0.0', + ref: 'origin/master', + repo: 'git@github.com:repo.git', + path: 'dist/server.js', + 'post-deploy': 'npm install && npm run build && pm2 reload ecosystem.config.js --only prod', + }, + }, +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..8edf5ba --- /dev/null +++ b/jest.config.js @@ -0,0 +1,12 @@ +const { pathsToModuleNameMapper } = require('ts-jest'); +const { compilerOptions } = require('./tsconfig.json'); + +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '/src' }), +}; diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..7cb62f6 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,40 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + upstream api-server { + server server:3000; + keepalive 100; + } + + server { + listen 80; + server_name localhost; + + location / { + proxy_http_version 1.1; + proxy_pass http://api-server; + } + + } + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + include /etc/nginx/conf.d/*.conf; +} diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..9c4580d --- /dev/null +++ b/nodemon.json @@ -0,0 +1,12 @@ +{ + "watch": [ + "src", + ".env" + ], + "ext": "js,ts,json", + "ignore": [ + "src/logs/*", + "src/**/*.{spec,test}.ts" + ], + "exec": "ts-node -r tsconfig-paths/register --transpile-only src/server.ts" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3cbe10c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11326 @@ +{ + "name": "GP-Backend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "GP-Backend", + "version": "0.0.0", + "license": "ISC", + "dependencies": { + "@prisma/client": "^4.1.0", + "bcrypt": "^5.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", + "compression": "^1.7.4", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "envalid": "^7.3.1", + "express": "^4.18.1", + "helmet": "^5.1.1", + "hpp": "^0.2.3", + "jsonwebtoken": "^8.5.1", + "morgan": "^1.10.0", + "reflect-metadata": "^0.1.13", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", + "typedi": "^0.10.0", + "winston": "^3.8.1", + "winston-daily-rotate-file": "^4.7.1" + }, + "devDependencies": { + "@swc/cli": "^0.1.57", + "@swc/core": "^1.2.220", + "@types/bcrypt": "^5.0.0", + "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.3", + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/hpp": "^0.2.2", + "@types/jest": "^28.1.6", + "@types/jsonwebtoken": "^8.5.8", + "@types/morgan": "^1.9.3", + "@types/node": "^17.0.45", + "@types/supertest": "^2.0.12", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "@typescript-eslint/eslint-plugin": "^5.29.0", + "@typescript-eslint/parser": "^5.29.0", + "cross-env": "^7.0.3", + "eslint": "^8.20.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.1", + "jest": "^28.1.1", + "lint-staged": "^13.0.3", + "node-config": "^0.0.2", + "node-gyp": "^9.1.0", + "nodemon": "^2.0.19", + "pm2": "^5.2.0", + "prettier": "^2.7.1", + "prisma": "^4.1.0", + "supertest": "^6.2.4", + "ts-jest": "^28.0.7", + "ts-node": "^10.9.1", + "tsc-alias": "^1.7.0", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.7.4" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "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 + }, + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@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, + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@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, + "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, + "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-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.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 + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "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/@cspotcode/source-map-support/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/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "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, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "dependencies": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "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/@mole-inc/bin-wrapper": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz", + "integrity": "sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==", + "dev": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^5.0.0", + "content-disposition": "^0.5.4", + "ext-name": "^5.0.0", + "file-type": "^17.1.6", + "filenamify": "^5.0.2", + "got": "^11.8.5", + "os-filter-obj": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pm2/agent": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.4.tgz", + "integrity": "sha512-n7WYvvTJhHLS2oBb1PjOtgLpMhgImOq8sXkPBw6smeg9LJBWZjiEgPKOpR8mn9UJZsB5P3W4V/MyvNnp31LKeA==", + "dev": true, + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.0.0-1", + "fclone": "~1.0.11", + "nssocket": "0.6.0", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.3.0", + "semver": "~7.5.0", + "ws": "~7.5.10" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", + "dev": true + }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/agent/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@pm2/io": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.0.1.tgz", + "integrity": "sha512-KiA+shC6sULQAr9mGZ1pg+6KVW9MF8NpG99x26Lf/082/Qy8qsTCtnJy+HQReW1A9Rdf0C/404cz0RZGZro+IA==", + "dev": true, + "dependencies": { + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "dev": true + }, + "node_modules/@pm2/io/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "node_modules/@pm2/io/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@pm2/js-api": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", + "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "dev": true, + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^7.0.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "dev": true + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "dev": true, + "dependencies": { + "debug": "^4.3.1" + } + }, + "node_modules/@prisma/client": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", + "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz", + "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==", + "dev": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", + "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@swc/cli": { + "version": "0.1.65", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.65.tgz", + "integrity": "sha512-4NcgsvJVHhA7trDnMmkGLLvWMHu2kSy+qHx6QwRhhJhdiYdNUrhdp+ERxen73sYtaeEOYeLJcWrQ60nzKi6rpg==", + "dev": true, + "dependencies": { + "@mole-inc/bin-wrapper": "^8.0.1", + "commander": "^7.1.0", + "fast-glob": "^3.2.5", + "minimatch": "^9.0.3", + "semver": "^7.3.8", + "slash": "3.0.0", + "source-map": "^0.7.3" + }, + "bin": { + "spack": "bin/spack.js", + "swc": "bin/swc.js", + "swcx": "bin/swcx.js" + }, + "engines": { + "node": ">= 12.13" + }, + "peerDependencies": { + "@swc/core": "^1.2.66", + "chokidar": "^3.5.1" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@swc/core": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.24" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", + "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", + "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "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/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@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-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "dev": true, + "peerDependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "dev": true, + "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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hpp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.6.tgz", + "integrity": "sha512-6gn1RuHA1/XFCVCqCkSV+AWy07YwtGg4re4SHhLMoiARTg9XlrbYMtVR+Uvws0VlERXzzcA+1UYvxEV6O+sgPg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true + }, + "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 + }, + "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, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "dependencies": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "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/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "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/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", + "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", + "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "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 + }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", + "integrity": "sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "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 + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "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/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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/accepts/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/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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/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/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amp": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", + "dev": true + }, + "node_modules/amp-message": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", + "dev": true, + "dependencies": { + "amp": "0.3.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==" + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "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==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "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 + }, + "node_modules/babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^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.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^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-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/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==" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", + "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "find-versions": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", + "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", + "dev": true, + "dependencies": { + "bin-version": "^6.0.0", + "semver": "^7.5.3", + "semver-truncate": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "dev": true, + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/bodec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", + "dev": true + }, + "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==", + "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==", + "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==" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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==" + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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, + "engines": { + "node": ">=10" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "dependencies": { + "libphonenumber-js": "^1.9.43", + "validator": "^13.7.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dev": true, + "dependencies": { + "chalk": "3.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/cli-tableau/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true + }, + "node_modules/cliui/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true + }, + "node_modules/color": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", + "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", + "dependencies": { + "color-convert": "^3.0.1", + "color-string": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/color-string": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", + "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "engines": { + "node": ">=12.20" + } + }, + "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/color/node_modules/color-convert": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", + "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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/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==", + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "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-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "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 + }, + "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/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/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", + "dev": true + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "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.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "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==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "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/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.240", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", + "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/envalid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/envalid/-/envalid-7.3.1.tgz", + "integrity": "sha512-KL1YRwn8WcoF/Ty7t+yLLtZol01xr9ZJMTjzoGRM8NaSU+nQQjSWOQKKJhJP2P57bpdakJ9jbxqQX4fGTOicZg==", + "dependencies": { + "tslib": "2.3.1" + }, + "engines": { + "node": ">=8.12" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "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==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/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, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz", + "integrity": "sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "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.12", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "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==", + "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==" + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extrareqp2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", + "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "dev": true + }, + "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 + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "dev": true + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/file-type": { + "version": "17.1.6", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.6.tgz", + "integrity": "sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==", + "dev": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.2", + "strtok3": "^7.0.0-alpha.9", + "token-types": "^5.0.0-alpha.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filenamify": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-5.1.1.tgz", + "integrity": "sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^3.0.0", + "strip-outer": "^2.0.0", + "trim-repeated": "^2.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "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==", + "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==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "dev": true, + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "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.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/gauge/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/gauge/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/gauge/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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/git-node-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", + "dev": true + }, + "node_modules/git-sha1": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", + "dev": true + }, + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/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/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.1.tgz", + "integrity": "sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "dependencies": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true + }, + "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==", + "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/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "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/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, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "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==", + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "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 + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "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/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runtime/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runtime/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-git": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", + "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", + "dev": true, + "dependencies": { + "bodec": "^0.1.0", + "culvert": "^0.1.2", + "git-sha1": "^0.1.2", + "pako": "^0.2.5" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "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 + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/lazy": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", + "integrity": "sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.24", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz", + "integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==" + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "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 + }, + "node_modules/lint-staged": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", + "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", + "dev": true, + "dependencies": { + "chalk": "5.3.0", + "commander": "11.0.0", + "debug": "4.3.4", + "execa": "7.2.0", + "lilconfig": "2.1.0", + "listr2": "6.6.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/lint-staged/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/lint-staged/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/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/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", + "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", + "rfdc": "^1.3.0", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "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==" + }, + "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==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "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==" + }, + "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==" + }, + "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==" + }, + "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==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/log-update": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/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/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/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "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==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "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 + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "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/mime-types/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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/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/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/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "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/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "dev": true + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "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 + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "dev": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "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==" + }, + "node_modules/node-config": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/node-config/-/node-config-0.0.2.tgz", + "integrity": "sha512-NZu10oQ7jN6eDkRK22YX8j87mS02CuarKqoWIPcU6MKbuQ5dfLkvjOsWyN4ov+hPkIR7BppEueUg3QtcsRO7MA==", + "dev": true, + "engines": { + "node": ">=0.1.99" + } + }, + "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/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/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==", + "dev": true + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/nssocket": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", + "integrity": "sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==", + "dev": true, + "dependencies": { + "eventemitter2": "~0.4.14", + "lazy": "~1.0.11" + }, + "engines": { + "node": ">= 0.10.x" + } + }, + "node_modules/nssocket/node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "dev": true + }, + "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-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "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/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "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 + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/peek-readable": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pidusage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", + "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/pm2": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.4.3.tgz", + "integrity": "sha512-4/I1htIHzZk1Y67UgOCo4F1cJtas1kSds31N8zN0PybO230id1nigyjGuGFzUnGmUFPmrJ0On22fO1ChFlp7VQ==", + "dev": true, + "dependencies": { + "@pm2/agent": "~2.0.0", + "@pm2/io": "~6.0.1", + "@pm2/js-api": "~0.8.0", + "@pm2/pm2-version-check": "latest", + "async": "~3.2.0", + "blessed": "0.1.81", + "chalk": "3.0.0", + "chokidar": "^3.5.3", + "cli-tableau": "^2.0.0", + "commander": "2.15.1", + "croner": "~4.1.92", + "dayjs": "~1.11.5", + "debug": "^4.3.1", + "enquirer": "2.3.6", + "eventemitter2": "5.0.1", + "fclone": "1.0.11", + "js-yaml": "~4.1.0", + "mkdirp": "1.0.4", + "needle": "2.4.0", + "pidusage": "~3.0", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.1", + "pm2-deploy": "~1.0.2", + "pm2-multimeter": "^0.1.2", + "promptly": "^2", + "semver": "^7.2", + "source-map-support": "0.5.21", + "sprintf-js": "1.1.2", + "vizion": "~2.2.1" + }, + "bin": { + "pm2": "bin/pm2", + "pm2-dev": "bin/pm2-dev", + "pm2-docker": "bin/pm2-docker", + "pm2-runtime": "bin/pm2-runtime" + }, + "engines": { + "node": ">=12.0.0" + }, + "optionalDependencies": { + "pm2-sysmonit": "^1.2.8" + } + }, + "node_modules/pm2-axon": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", + "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", + "dev": true, + "dependencies": { + "amp": "~0.3.1", + "amp-message": "~0.1.1", + "debug": "^4.3.1", + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-axon-rpc": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", + "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", + "dev": true, + "dependencies": { + "debug": "^4.3.1" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-deploy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "dev": true, + "dependencies": { + "run-series": "^1.1.8", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pm2-multimeter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", + "dev": true, + "dependencies": { + "charm": "~0.1.1" + } + }, + "node_modules/pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "dev": true, + "optional": true, + "dependencies": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + } + }, + "node_modules/pm2-sysmonit/node_modules/pidusage": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", + "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pm2/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pm2/node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "node_modules/pm2/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pm2/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prisma": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz", + "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "4.16.2" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promptly": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", + "dev": true, + "dependencies": { + "read": "^1.0.4" + } + }, + "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, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pstree.remy": { + "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 + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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==", + "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/react-is": { + "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 + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "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/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "dev": true, + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "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/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "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==", + "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==" + }, + "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==", + "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==", + "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-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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "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/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "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, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "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/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-2.0.0.tgz", + "integrity": "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", + "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.1.3" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/superagent": { + "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 superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "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, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.1.2" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/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/swagger-jsdoc/node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.29.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.5.tgz", + "integrity": "sha512-2zFnjONgLXlz8gLToRKvXHKJdqXF6UGgCmv65i8T6i/UrjDNyV1fIQ7FauZA40SaivlGKEvW2tw9XDyDhfcXqQ==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "dependencies": { + "swagger-ui-dist": ">=4.11.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/systeminformation": { + "version": "5.27.11", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.11.tgz", + "integrity": "sha512-K3Lto/2m3K2twmKHdgx5B+0in9qhXK4YnoT9rIlgwN/4v7OV5c8IjbeAUkuky/6VzCQC7iKCAqi8rZathCdjHg==", + "dev": true, + "optional": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "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/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/tar/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/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "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-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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", + "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/trim-repeated": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", + "integrity": "sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-jest": { + "version": "28.0.8", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", + "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^28.0.0", + "babel-jest": "^28.0.0", + "jest": "^28.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "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/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tx2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", + "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", + "dev": true, + "optional": true, + "dependencies": { + "json-stringify-safe": "^5.0.1" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedi": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.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==" + }, + "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==", + "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/v8-to-istanbul": { + "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, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "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 + }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "engines": { + "node": ">= 0.10" + } + }, + "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/vizion": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", + "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", + "dev": true, + "dependencies": { + "async": "^2.6.3", + "git-node-fs": "^1.0.0", + "ini": "^1.3.5", + "js-git": "^0.7.8" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vizion/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/wide-align/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/wide-align/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/wide-align/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/winston": { + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", + "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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/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, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/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==", + "dev": true + }, + "node_modules/yargs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..df3861f --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "GP-Backend", + "version": "0.0.0", + "description": "TypeScript + Prisma + MySQL + Express API Server", + "author": "", + "license": "ISC", + "scripts": { + "start": "npm run build && cross-env NODE_ENV=production node dist/server.js", + "dev": "cross-env NODE_ENV=development nodemon", + "build": "swc src -d dist --source-maps --copy-files", + "build:tsc": "tsc && tsc-alias", + "test": "jest --forceExit --detectOpenHandles", + "lint": "eslint --ignore-path .gitignore --ext .ts src/", + "lint:fix": "npm run lint -- --fix", + "prisma:init": "prisma init", + "prisma:migrate": "prisma migrate dev --preview-feature", + "prisma:generate": "prisma generate", + "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", + "deploy:dev": "pm2 start ecosystem.config.js --only dev" + }, + "prisma": { + "schema": "src/prisma/schema.prisma" + }, + "dependencies": { + "@prisma/client": "^4.1.0", + "bcrypt": "^5.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", + "compression": "^1.7.4", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "envalid": "^7.3.1", + "express": "^4.18.1", + "helmet": "^5.1.1", + "hpp": "^0.2.3", + "jsonwebtoken": "^8.5.1", + "morgan": "^1.10.0", + "reflect-metadata": "^0.1.13", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", + "typedi": "^0.10.0", + "winston": "^3.8.1", + "winston-daily-rotate-file": "^4.7.1" + }, + "devDependencies": { + "@swc/cli": "^0.1.57", + "@swc/core": "^1.2.220", + "@types/bcrypt": "^5.0.0", + "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.3", + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/hpp": "^0.2.2", + "@types/jest": "^28.1.6", + "@types/jsonwebtoken": "^8.5.8", + "@types/morgan": "^1.9.3", + "@types/node": "^17.0.45", + "@types/supertest": "^2.0.12", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "@typescript-eslint/eslint-plugin": "^5.29.0", + "@typescript-eslint/parser": "^5.29.0", + "cross-env": "^7.0.3", + "eslint": "^8.20.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.1", + "jest": "^28.1.1", + "lint-staged": "^13.0.3", + "node-config": "^0.0.2", + "node-gyp": "^9.1.0", + "nodemon": "^2.0.19", + "pm2": "^5.2.0", + "prettier": "^2.7.1", + "prisma": "^4.1.0", + "supertest": "^6.2.4", + "ts-jest": "^28.0.7", + "ts-node": "^10.9.1", + "tsc-alias": "^1.7.0", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.7.4" + } +} \ No newline at end of file diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..d5c6fc3 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,81 @@ +import 'reflect-metadata'; +import compression from 'compression'; +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +import express from 'express'; +import helmet from 'helmet'; +import hpp from 'hpp'; +import morgan from 'morgan'; +import swaggerJSDoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +import { NODE_ENV, PORT, LOG_FORMAT, ORIGIN, CREDENTIALS } from '@config'; +import { Routes } from '@interfaces/routes.interface'; +import { ErrorMiddleware } from '@middlewares/error.middleware'; +import { logger, stream } from '@utils/logger'; + +export class App { + public app: express.Application; + public env: string; + public port: string | number; + + constructor(routes: Routes[]) { + this.app = express(); + this.env = NODE_ENV || 'development'; + this.port = PORT || 3000; + + this.initializeMiddlewares(); + this.initializeRoutes(routes); + this.initializeSwagger(); + this.initializeErrorHandling(); + } + + public listen() { + this.app.listen(this.port, () => { + logger.info(`=================================`); + logger.info(`======= ENV: ${this.env} =======`); + logger.info(`🚀 App listening on the port ${this.port}`); + logger.info(`=================================`); + }); + } + + public getServer() { + return this.app; + } + + private initializeMiddlewares() { + this.app.use(morgan(LOG_FORMAT, { stream })); + this.app.use(cors({ origin: ORIGIN, credentials: CREDENTIALS })); + this.app.use(hpp()); + this.app.use(helmet()); + this.app.use(compression()); + this.app.use(express.json()); + this.app.use(express.urlencoded({ extended: true })); + this.app.use(cookieParser()); + } + + private initializeRoutes(routes: Routes[]) { + routes.forEach(route => { + this.app.use('/', route.router); + }); + } + + private initializeSwagger() { + const options = { + swaggerDefinition: { + info: { + title: 'REST API', + version: '1.0.0', + description: 'Example docs', + }, + }, + apis: ['swagger.yaml'], + }; + + const specs = swaggerJSDoc(options); + this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); + } + + private initializeErrorHandling() { + this.app.use(ErrorMiddleware); + } +} diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..ef17df5 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,5 @@ +import { config } from 'dotenv'; +config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); + +export const CREDENTIALS = process.env.CREDENTIALS === 'true'; +export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN } = process.env; diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts new file mode 100644 index 0000000..ab035db --- /dev/null +++ b/src/controllers/auth.controller.ts @@ -0,0 +1,44 @@ +import { NextFunction, Request, Response } from 'express'; +import { Container } from 'typedi'; +import { RequestWithUser } from '@interfaces/auth.interface'; +import { User } from '@interfaces/users.interface'; +import { AuthService } from '@services/auth.service'; + +export class AuthController { + public auth = Container.get(AuthService); + + public signUp = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userData: User = req.body; + const signUpUserData: User = await this.auth.signup(userData); + + res.status(201).json({ data: signUpUserData, message: 'signup' }); + } catch (error) { + next(error); + } + }; + + public logIn = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userData: User = req.body; + const { cookie, findUser } = await this.auth.login(userData); + + res.setHeader('Set-Cookie', [cookie]); + res.status(200).json({ data: findUser, message: 'login' }); + } catch (error) { + next(error); + } + }; + + public logOut = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const userData: User = req.user; + const logOutUserData: User = await this.auth.logout(userData); + + res.setHeader('Set-Cookie', ['Authorization=; Max-age=0']); + res.status(200).json({ data: logOutUserData, message: 'logout' }); + } catch (error) { + next(error); + } + }; +} diff --git a/src/controllers/users.controller.ts b/src/controllers/users.controller.ts new file mode 100644 index 0000000..a3b5d4d --- /dev/null +++ b/src/controllers/users.controller.ts @@ -0,0 +1,63 @@ +import { NextFunction, Request, Response } from 'express'; +import { Container } from 'typedi'; +import { User } from '@interfaces/users.interface'; +import { UserService } from '@services/users.service'; + +export class UserController { + public user = Container.get(UserService); + + public getUsers = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const findAllUsersData: User[] = await this.user.findAllUser(); + + res.status(200).json({ data: findAllUsersData, message: 'findAll' }); + } catch (error) { + next(error); + } + }; + + public getUserById = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userId = Number(req.params.id); + const findOneUserData: User = await this.user.findUserById(userId); + + res.status(200).json({ data: findOneUserData, message: 'findOne' }); + } catch (error) { + next(error); + } + }; + + public createUser = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userData: User = req.body; + const createUserData: User = await this.user.createUser(userData); + + res.status(201).json({ data: createUserData, message: 'created' }); + } catch (error) { + next(error); + } + }; + + public updateUser = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userId = Number(req.params.id); + const userData: User = req.body; + const updateUserData: User = await this.user.updateUser(userId, userData); + + res.status(200).json({ data: updateUserData, message: 'updated' }); + } catch (error) { + next(error); + } + }; + + public deleteUser = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userId = Number(req.params.id); + const deleteUserData: User = await this.user.deleteUser(userId); + + res.status(200).json({ data: deleteUserData, message: 'deleted' }); + } catch (error) { + next(error); + } + }; +} diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts new file mode 100644 index 0000000..5bf903b --- /dev/null +++ b/src/dtos/users.dto.ts @@ -0,0 +1,20 @@ +import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength } from 'class-validator'; + +export class CreateUserDto { + @IsEmail() + public email: string; + + @IsString() + @IsNotEmpty() + @MinLength(9) + @MaxLength(32) + public password: string; +} + +export class UpdateUserDto { + @IsString() + @IsNotEmpty() + @MinLength(9) + @MaxLength(32) + public password: string; +} diff --git a/src/exceptions/HttpException.ts b/src/exceptions/HttpException.ts new file mode 100644 index 0000000..f0ae6aa --- /dev/null +++ b/src/exceptions/HttpException.ts @@ -0,0 +1,10 @@ +export class HttpException extends Error { + public status: number; + public message: string; + + constructor(status: number, message: string) { + super(message); + this.status = status; + this.message = message; + } +} diff --git a/src/http/auth.http b/src/http/auth.http new file mode 100644 index 0000000..2198991 --- /dev/null +++ b/src/http/auth.http @@ -0,0 +1,27 @@ +# baseURL +@baseURL = http://localhost:3000 + +### +# User Signup +POST {{ baseURL }}/signup +Content-Type: application/json + +{ + "email": "example@email.com", + "password": "password" +} + +### +# User Login +POST {{ baseURL }}/login +Content-Type: application/json + +{ + "email": "example@email.com", + "password": "password" +} + +### +# User Logout +POST {{ baseURL }}/logout +Content-Type: application/json diff --git a/src/http/users.http b/src/http/users.http new file mode 100644 index 0000000..13209f2 --- /dev/null +++ b/src/http/users.http @@ -0,0 +1,34 @@ +# baseURL +@baseURL = http://localhost:3000 + +### +# Find All Users +GET {{ baseURL }}/users + +### +# Find User By Id +GET {{ baseURL }}/users/1 + +### +# Create User +POST {{ baseURL }}/users +Content-Type: application/json + +{ + "email": "example@email.com", + "password": "password" +} + +### +# Modify User By Id +PUT {{ baseURL }}/users/1 +Content-Type: application/json + +{ + "email": "example@email.com", + "password": "password" +} + +### +# Delete User By Id +DELETE {{ baseURL }}/users/1 diff --git a/src/interfaces/auth.interface.ts b/src/interfaces/auth.interface.ts new file mode 100644 index 0000000..83947b8 --- /dev/null +++ b/src/interfaces/auth.interface.ts @@ -0,0 +1,15 @@ +import { Request } from 'express'; +import { User } from '@interfaces/users.interface'; + +export interface DataStoredInToken { + id: number; +} + +export interface TokenData { + token: string; + expiresIn: number; +} + +export interface RequestWithUser extends Request { + user: User; +} diff --git a/src/interfaces/routes.interface.ts b/src/interfaces/routes.interface.ts new file mode 100644 index 0000000..0f7005c --- /dev/null +++ b/src/interfaces/routes.interface.ts @@ -0,0 +1,6 @@ +import { Router } from 'express'; + +export interface Routes { + path?: string; + router: Router; +} diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts new file mode 100644 index 0000000..773e71f --- /dev/null +++ b/src/interfaces/users.interface.ts @@ -0,0 +1,5 @@ +export interface User { + id?: number; + email: string; + password: string; + } diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts new file mode 100644 index 0000000..9c803e9 --- /dev/null +++ b/src/middlewares/auth.middleware.ts @@ -0,0 +1,39 @@ +import { PrismaClient } from '@prisma/client'; +import { NextFunction, Response } from 'express'; +import { verify } from 'jsonwebtoken'; +import { SECRET_KEY } from '@config'; +import { HttpException } from '@exceptions/httpException'; +import { DataStoredInToken, RequestWithUser } from '@interfaces/auth.interface'; + +const getAuthorization = (req) => { + const coockie = req.cookies['Authorization']; + if (coockie) return coockie; + + const header = req.header('Authorization'); + if (header) return header.split('Bearer ')[1]; + + return null; +} + +export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: NextFunction) => { + try { + const Authorization = getAuthorization(req); + + if (Authorization) { + const { id } = (await verify(Authorization, SECRET_KEY)) as DataStoredInToken; + const users = new PrismaClient().user; + const findUser = await users.findUnique({ where: { id: Number(id) } }); + + if (findUser) { + req.user = findUser; + next(); + } else { + next(new HttpException(401, 'Wrong authentication token')); + } + } else { + next(new HttpException(404, 'Authentication token missing')); + } + } catch (error) { + next(new HttpException(401, 'Wrong authentication token')); + } +}; diff --git a/src/middlewares/error.middleware.ts b/src/middlewares/error.middleware.ts new file mode 100644 index 0000000..1dbeb17 --- /dev/null +++ b/src/middlewares/error.middleware.ts @@ -0,0 +1,15 @@ +import { NextFunction, Request, Response } from 'express'; +import { HttpException } from '@exceptions/httpException'; +import { logger } from '@utils/logger'; + +export const ErrorMiddleware = (error: HttpException, req: Request, res: Response, next: NextFunction) => { + try { + const status: number = error.status || 500; + const message: string = error.message || 'Something went wrong'; + + logger.error(`[${req.method}] ${req.path} >> StatusCode:: ${status}, Message:: ${message}`); + res.status(status).json({ message }); + } catch (error) { + next(error); + } +}; diff --git a/src/middlewares/validation.middleware.ts b/src/middlewares/validation.middleware.ts new file mode 100644 index 0000000..d427392 --- /dev/null +++ b/src/middlewares/validation.middleware.ts @@ -0,0 +1,27 @@ +import { plainToInstance } from 'class-transformer'; +import { validateOrReject, ValidationError } from 'class-validator'; +import { NextFunction, Request, Response } from 'express'; +import { HttpException } from '@exceptions/httpException'; + +/** + * @name ValidationMiddleware + * @description Allows use of decorator and non-decorator based validation + * @param type dto + * @param skipMissingProperties When skipping missing properties + * @param whitelist Even if your object is an instance of a validation class it can contain additional properties that are not defined + * @param forbidNonWhitelisted If you would rather to have an error thrown when any non-whitelisted properties are present + */ +export const ValidationMiddleware = (type: any, skipMissingProperties = false, whitelist = false, forbidNonWhitelisted = false) => { + return (req: Request, res: Response, next: NextFunction) => { + const dto = plainToInstance(type, req.body); + validateOrReject(dto, { skipMissingProperties, whitelist, forbidNonWhitelisted }) + .then(() => { + req.body = dto; + next(); + }) + .catch((errors: ValidationError[]) => { + const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); + next(new HttpException(400, message)); + }); + }; +}; diff --git a/src/prisma/migrations/20210314081925_initial/migration.sql b/src/prisma/migrations/20210314081925_initial/migration.sql new file mode 100644 index 0000000..ca07fb7 --- /dev/null +++ b/src/prisma/migrations/20210314081925_initial/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE `User` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `email` VARCHAR(191) NOT NULL, + `password` VARCHAR(191) NOT NULL, +UNIQUE INDEX `User.email_unique`(`email`), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/src/prisma/migrations/migration_lock.toml b/src/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..e5a788a --- /dev/null +++ b/src/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "mysql" \ No newline at end of file diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma new file mode 100644 index 0000000..b77a8dc --- /dev/null +++ b/src/prisma/schema.prisma @@ -0,0 +1,17 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + password String +} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts new file mode 100644 index 0000000..a32969f --- /dev/null +++ b/src/routes/auth.route.ts @@ -0,0 +1,22 @@ +import { Router } from 'express'; +import { AuthController } from '@controllers/auth.controller'; +import { CreateUserDto } from '@dtos/users.dto'; +import { Routes } from '@interfaces/routes.interface'; +import { AuthMiddleware } from '@middlewares/auth.middleware'; +import { ValidationMiddleware } from '@middlewares/validation.middleware'; + +export class AuthRoute implements Routes { + public path = '/'; + public router = Router(); + public auth = new AuthController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.post(`${this.path}signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); + this.router.post(`${this.path}login`, ValidationMiddleware(CreateUserDto), this.auth.logIn); + this.router.post(`${this.path}logout`, AuthMiddleware, this.auth.logOut); + } +} diff --git a/src/routes/users.route.ts b/src/routes/users.route.ts new file mode 100644 index 0000000..b750b9f --- /dev/null +++ b/src/routes/users.route.ts @@ -0,0 +1,23 @@ +import { Router } from 'express'; +import { UserController } from '@controllers/users.controller'; +import { CreateUserDto } from '@dtos/users.dto'; +import { Routes } from '@interfaces/routes.interface'; +import { ValidationMiddleware } from '@middlewares/validation.middleware'; + +export class UserRoute implements Routes { + public path = '/users'; + public router = Router(); + public user = new UserController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.get(`${this.path}`, this.user.getUsers); + this.router.get(`${this.path}/:id(\\d+)`, this.user.getUserById); + this.router.post(`${this.path}`, ValidationMiddleware(CreateUserDto), this.user.createUser); + this.router.put(`${this.path}/:id(\\d+)`, ValidationMiddleware(CreateUserDto, true), this.user.updateUser); + this.router.delete(`${this.path}/:id(\\d+)`, this.user.deleteUser); + } +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..2362805 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,10 @@ +import { App } from '@/app'; +import { AuthRoute } from '@routes/auth.route'; +import { UserRoute } from '@routes/users.route'; +import { ValidateEnv } from '@utils/validateEnv'; + +ValidateEnv(); + +const app = new App([new UserRoute(), new AuthRoute()]); + +app.listen(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..f721645 --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,56 @@ +import { PrismaClient } from '@prisma/client'; +import { compare, hash } from 'bcrypt'; +import { sign } from 'jsonwebtoken'; +import { Service } from 'typedi'; +import { SECRET_KEY } from '@config'; +import { CreateUserDto } from '@dtos/users.dto'; +import { HttpException } from '@exceptions/httpException'; +import { DataStoredInToken, TokenData } from '@interfaces/auth.interface'; +import { User } from '@interfaces/users.interface'; + +@Service() +export class AuthService { + public users = new PrismaClient().user; + + public async signup(userData: CreateUserDto): Promise { + const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); + if (findUser) throw new HttpException(409, `This email ${userData.email} already exists`); + + const hashedPassword = await hash(userData.password, 10); + const createUserData: Promise = this.users.create({ data: { ...userData, password: hashedPassword } }); + + return createUserData; + } + + public async login(userData: CreateUserDto): Promise<{ cookie: string; findUser: User }> { + const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); + if (!findUser) throw new HttpException(409, `This email ${userData.email} was not found`); + + const isPasswordMatching: boolean = await compare(userData.password, findUser.password); + if (!isPasswordMatching) throw new HttpException(409, "Password is not matching"); + + const tokenData = this.createToken(findUser); + const cookie = this.createCookie(tokenData); + + return { cookie, findUser }; + } + + public async logout(userData: User): Promise { + const findUser: User = await this.users.findFirst({ where: { email: userData.email, password: userData.password } }); + if (!findUser) throw new HttpException(409, "User doesn't exist"); + + return findUser; + } + + public createToken(user: User): TokenData { + const dataStoredInToken: DataStoredInToken = { id: user.id }; + const secretKey: string = SECRET_KEY; + const expiresIn: number = 60 * 60; + + return { expiresIn, token: sign(dataStoredInToken, secretKey, { expiresIn }) }; + } + + public createCookie(tokenData: TokenData): string { + return `Authorization=${tokenData.token}; HttpOnly; Max-Age=${tokenData.expiresIn};`; + } +} diff --git a/src/services/users.service.ts b/src/services/users.service.ts new file mode 100644 index 0000000..d027d7a --- /dev/null +++ b/src/services/users.service.ts @@ -0,0 +1,49 @@ +import { PrismaClient } from '@prisma/client'; +import { hash } from 'bcrypt'; +import { Service } from 'typedi'; +import { CreateUserDto } from '@dtos/users.dto'; +import { HttpException } from '@/exceptions/httpException'; +import { User } from '@interfaces/users.interface'; + +@Service() +export class UserService { + public user = new PrismaClient().user; + + public async findAllUser(): Promise { + const allUser: User[] = await this.user.findMany(); + return allUser; + } + + public async findUserById(userId: number): Promise { + const findUser: User = await this.user.findUnique({ where: { id: userId } }); + if (!findUser) throw new HttpException(409, "User doesn't exist"); + + return findUser; + } + + public async createUser(userData: CreateUserDto): Promise { + const findUser: User = await this.user.findUnique({ where: { email: userData.email } }); + if (findUser) throw new HttpException(409, `This email ${userData.email} already exists`); + + const hashedPassword = await hash(userData.password, 10); + const createUserData: User = await this.user.create({ data: { ...userData, password: hashedPassword } }); + return createUserData; + } + + public async updateUser(userId: number, userData: CreateUserDto): Promise { + const findUser: User = await this.user.findUnique({ where: { id: userId } }); + if (!findUser) throw new HttpException(409, "User doesn't exist"); + + const hashedPassword = await hash(userData.password, 10); + const updateUserData = await this.user.update({ where: { id: userId }, data: { ...userData, password: hashedPassword } }); + return updateUserData; + } + + public async deleteUser(userId: number): Promise { + const findUser: User = await this.user.findUnique({ where: { id: userId } }); + if (!findUser) throw new HttpException(409, "User doesn't exist"); + + const deleteUserData = await this.user.delete({ where: { id: userId } }); + return deleteUserData; + } +} diff --git a/src/test/auth.test.ts b/src/test/auth.test.ts new file mode 100644 index 0000000..f2d07b4 --- /dev/null +++ b/src/test/auth.test.ts @@ -0,0 +1,81 @@ +import { User } from '@prisma/client'; +import bcrypt from 'bcrypt'; +import request from 'supertest'; +import App from '@/app'; +import { CreateUserDto } from '@dtos/users.dto'; +import AuthRoute from '@routes/auth.route'; + +afterAll(async () => { + await new Promise(resolve => setTimeout(() => resolve(), 500)); +}); + +describe('Testing Auth', () => { + describe('[POST] /signup', () => { + it('response should have the Create userData', async () => { + const userData: CreateUserDto = { + email: 'test@email.com', + password: 'q1w2e3r4', + }; + + const authRoute = new AuthRoute(); + const users = authRoute.authController.authService.users; + + users.findUnique = jest.fn().mockReturnValue(null); + users.create = jest.fn().mockReturnValue({ + id: 1, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + + const app = new App([authRoute]); + return request(app.getServer()).post(`${authRoute.path}signup`).send(userData).expect(201); + }); + }); + + describe('[POST] /login', () => { + it('response should have the Set-Cookie header with the Authorization token', async () => { + const userData: CreateUserDto = { + email: 'test@email.com', + password: 'q1w2e3r4', + }; + + const authRoute = new AuthRoute(); + const users = authRoute.authController.authService.users; + + users.findUnique = jest.fn().mockReturnValue({ + id: 1, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + + const app = new App([authRoute]); + return request(app.getServer()) + .post(`${authRoute.path}login`) + .send(userData) + .expect('Set-Cookie', /^Authorization=.+/); + }); + }); + + // describe('[POST] /logout', () => { + // it('logout Set-Cookie Authorization=; Max-age=0', async () => { + // const user: User = { + // id: 1, + // email: 'test@email.com', + // password: 'q1w2e3r4', + // }; + + // const authRoute = new AuthRoute(); + // const users = authRoute.authController.authService.users; + + // users.findFirst = jest.fn().mockReturnValue({ + // ...user, + // password: await bcrypt.hash(user.password, 10), + // }); + + // const app = new App([authRoute]); + // return request(app.getServer()) + // .post(`${authRoute.path}logout`) + // .expect('Set-Cookie', /^Authorization=\;/); + // }); + // }); +}); diff --git a/src/test/index.test.ts b/src/test/index.test.ts new file mode 100644 index 0000000..8b3825c --- /dev/null +++ b/src/test/index.test.ts @@ -0,0 +1,18 @@ +import request from 'supertest'; +import App from '@/app'; +import IndexRoute from '@routes/index.route'; + +afterAll(async () => { + await new Promise(resolve => setTimeout(() => resolve(), 500)); +}); + +describe('Testing Index', () => { + describe('[GET] /', () => { + it('response statusCode 200', () => { + const indexRoute = new IndexRoute(); + const app = new App([indexRoute]); + + return request(app.getServer()).get(`${indexRoute.path}`).expect(200); + }); + }); +}); diff --git a/src/test/users.test.ts b/src/test/users.test.ts new file mode 100644 index 0000000..8bf00fd --- /dev/null +++ b/src/test/users.test.ts @@ -0,0 +1,134 @@ +import { PrismaClient, User } from '@prisma/client'; +import bcrypt from 'bcrypt'; +import request from 'supertest'; +import App from '@/app'; +import { CreateUserDto } from '@dtos/users.dto'; +import UserRoute from '@routes/users.route'; + +afterAll(async () => { + await new Promise(resolve => setTimeout(() => resolve(), 500)); +}); + +describe('Testing Users', () => { + describe('[GET] /users', () => { + it('response findAll users', async () => { + const usersRoute = new UserRoute(); + const users = usersRoute.usersController.userService.users; + + users.findMany = jest.fn().mockReturnValue([ + { + id: 1, + email: 'a@email.com', + password: await bcrypt.hash('q1w2e3r4!', 10), + }, + { + id: 2, + email: 'b@email.com', + password: await bcrypt.hash('a1s2d3f4!', 10), + }, + { + id: 3, + email: 'c@email.com', + password: await bcrypt.hash('z1x2c3v4!', 10), + }, + ]); + + const app = new App([usersRoute]); + return request(app.getServer()).get(`${usersRoute.path}`).expect(200); + }); + }); + + describe('[GET] /users/:id', () => { + it('response findOne user', async () => { + const userId = 1; + + const usersRoute = new UserRoute(); + const users = usersRoute.usersController.userService.users; + + users.findUnique = jest.fn().mockReturnValue({ + id: 1, + email: 'a@email.com', + password: await bcrypt.hash('q1w2e3r4!', 10), + }); + + const app = new App([usersRoute]); + return request(app.getServer()).get(`${usersRoute.path}/${userId}`).expect(200); + }); + }); + + describe('[POST] /users', () => { + it('response Create user', async () => { + const userData: CreateUserDto = { + email: 'test@email.com', + password: 'q1w2e3r4', + }; + + const usersRoute = new UserRoute(); + const users = usersRoute.usersController.userService.users; + + users.findUnique = jest.fn().mockReturnValue(null); + users.create = jest.fn().mockReturnValue({ + id: 1, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + + const app = new App([usersRoute]); + return request(app.getServer()).post(`${usersRoute.path}`).send(userData).expect(201); + }); + }); + + describe('[PUT] /users/:id', () => { + it('response Update user', async () => { + const userId = 1; + const userData: CreateUserDto = { + email: 'test@email.com', + password: 'q1w2e3r4', + }; + + const usersRoute = new UserRoute(); + const users = usersRoute.usersController.userService.users; + + users.findUnique = jest.fn().mockReturnValue({ + id: userId, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + users.update = jest.fn().mockReturnValue({ + id: userId, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + + const app = new App([usersRoute]); + return request(app.getServer()).put(`${usersRoute.path}/${userId}`).send(userData).expect(200); + }); + }); + + describe('[DELETE] /users/:id', () => { + it('response Delete user', async () => { + const userId = 1; + const userData: CreateUserDto = { + email: 'test@email.com', + password: 'q1w2e3r4', + }; + + const usersRoute = new UserRoute(); + const users = usersRoute.usersController.userService.users; + + users.findUnique = jest.fn().mockReturnValue({ + id: userId, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + users.delete = jest.fn().mockReturnValue({ + id: userId, + email: userData.email, + password: await bcrypt.hash(userData.password, 10), + }); + + const app = new App([usersRoute]); + return request(app.getServer()).delete(`${usersRoute.path}/${userId}`).expect(200); + }); + }); +}); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..b8f43a9 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,65 @@ +import { existsSync, mkdirSync } from 'fs'; +import { join } from 'path'; +import winston from 'winston'; +import winstonDaily from 'winston-daily-rotate-file'; +import { LOG_DIR } from '@config'; + +// logs dir +const logDir: string = join(__dirname, LOG_DIR); + +if (!existsSync(logDir)) { + mkdirSync(logDir); +} + +// Define log format +const logFormat = winston.format.printf(({ timestamp, level, message }) => `${timestamp} ${level}: ${message}`); + +/* + * Log Level + * error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 + */ +const logger = winston.createLogger({ + format: winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + logFormat, + ), + transports: [ + // debug log setting + new winstonDaily({ + level: 'debug', + datePattern: 'YYYY-MM-DD', + dirname: logDir + '/debug', // log file /logs/debug/*.log in save + filename: `%DATE%.log`, + maxFiles: 30, // 30 Days saved + json: false, + zippedArchive: true, + }), + // error log setting + new winstonDaily({ + level: 'error', + datePattern: 'YYYY-MM-DD', + dirname: logDir + '/error', // log file /logs/error/*.log in save + filename: `%DATE%.log`, + maxFiles: 30, // 30 Days saved + handleExceptions: true, + json: false, + zippedArchive: true, + }), + ], +}); + +logger.add( + new winston.transports.Console({ + format: winston.format.combine(winston.format.splat(), winston.format.colorize()), + }), +); + +const stream = { + write: (message: string) => { + logger.info(message.substring(0, message.lastIndexOf('\n'))); + }, +}; + +export { logger, stream }; diff --git a/src/utils/validateEnv.ts b/src/utils/validateEnv.ts new file mode 100644 index 0000000..a6f1904 --- /dev/null +++ b/src/utils/validateEnv.ts @@ -0,0 +1,8 @@ +import { cleanEnv, port, str } from 'envalid'; + +export const ValidateEnv = () => { + cleanEnv(process.env, { + NODE_ENV: str(), + PORT: port(), + }); +}; diff --git a/swagger.yaml b/swagger.yaml new file mode 100644 index 0000000..eebcb31 --- /dev/null +++ b/swagger.yaml @@ -0,0 +1,123 @@ +tags: +- name: users + description: users API + +paths: +# [GET] users + /users: + get: + tags: + - users + summary: Find All Users + responses: + 200: + description: 'OK' + 500: + description: 'Server Error' + +# [POST] users + post: + tags: + - users + summary: Add User + parameters: + - name: body + in: body + description: user Data + required: true + schema: + $ref: '#/definitions/users' + responses: + 201: + description: 'Created' + 400: + description: 'Bad Request' + 409: + description: 'Conflict' + 500: + description: 'Server Error' + +# [GET] users/id + /users/{id}: + get: + tags: + - users + summary: Find User By Id + parameters: + - name: id + in: path + description: User Id + required: true + type: integer + responses: + 200: + description: 'OK' + 409: + description: 'Conflict' + 500: + description: 'Server Error' + +# [PUT] users/id + put: + tags: + - users + summary: Update User By Id + parameters: + - name: id + in: path + description: user Id + required: true + type: integer + - name: body + in: body + description: user Data + required: true + schema: + $ref: '#/definitions/users' + responses: + 200: + description: 'OK' + 400: + description: 'Bad Request' + 409: + description: 'Conflict' + 500: + description: 'Server Error' + +# [DELETE] users/id + delete: + tags: + - users + summary: Delete User By Id + parameters: + - name: id + in: path + description: user Id + required: true + type: integer + responses: + 200: + description: 'OK' + 409: + description: 'Conflict' + 500: + description: 'Server Error' + +# definitions +definitions: + users: + type: object + required: + - email + - password + properties: + email: + type: string + description: user Email + password: + type: string + description: user Password + +schemes: + - https + - http diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..18885ab --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "target": "es2017", + "lib": ["es2017", "esnext.asynciterable"], + "typeRoots": ["node_modules/@types"], + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "module": "commonjs", + "pretty": true, + "sourceMap": true, + "declaration": true, + "outDir": "dist", + "allowJs": true, + "noEmit": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "importHelpers": true, + "baseUrl": "src", + "paths": { + "@/*": ["*"], + "@config": ["config"], + "@controllers/*": ["controllers/*"], + "@dtos/*": ["dtos/*"], + "@exceptions/*": ["exceptions/*"], + "@interfaces/*": ["interfaces/*"], + "@middlewares/*": ["middlewares/*"], + "@routes/*": ["routes/*"], + "@services/*": ["services/*"], + "@utils/*": ["utils/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.json", ".env"], + "exclude": ["node_modules", "src/http", "src/logs"] +} From ea1ce0c201583ce15a3aa0727513ad98bec94ea1 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 25 Oct 2025 22:35:06 +0300 Subject: [PATCH 002/210] initial db schema and some imports fixing --- src/interfaces/users.interface.ts | 8 +- src/middlewares/auth.middleware.ts | 12 +- src/middlewares/error.middleware.ts | 2 +- src/middlewares/validation.middleware.ts | 2 +- src/prisma/schema.prisma | 197 ++++++++++++++++++++++- src/services/auth.service.ts | 4 +- src/services/users.service.ts | 2 +- 7 files changed, 205 insertions(+), 22 deletions(-) diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 773e71f..2f1dac4 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -1,5 +1,5 @@ export interface User { - id?: number; - email: string; - password: string; - } + id?: number; + email: string; + password: string; +} diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 9c803e9..113af89 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -1,19 +1,19 @@ import { PrismaClient } from '@prisma/client'; -import { NextFunction, Response } from 'express'; +import { NextFunction, Response, Request } from 'express'; import { verify } from 'jsonwebtoken'; import { SECRET_KEY } from '@config'; -import { HttpException } from '@exceptions/httpException'; +import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, RequestWithUser } from '@interfaces/auth.interface'; -const getAuthorization = (req) => { - const coockie = req.cookies['Authorization']; - if (coockie) return coockie; +const getAuthorization = (req: Request) => { + const cookie = req.cookies['Authorization']; + if (cookie) return cookie; const header = req.header('Authorization'); if (header) return header.split('Bearer ')[1]; return null; -} +}; export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: NextFunction) => { try { diff --git a/src/middlewares/error.middleware.ts b/src/middlewares/error.middleware.ts index 1dbeb17..b8e1b8e 100644 --- a/src/middlewares/error.middleware.ts +++ b/src/middlewares/error.middleware.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from 'express'; -import { HttpException } from '@exceptions/httpException'; +import { HttpException } from '@exceptions/HttpException'; import { logger } from '@utils/logger'; export const ErrorMiddleware = (error: HttpException, req: Request, res: Response, next: NextFunction) => { diff --git a/src/middlewares/validation.middleware.ts b/src/middlewares/validation.middleware.ts index d427392..ca6ee22 100644 --- a/src/middlewares/validation.middleware.ts +++ b/src/middlewares/validation.middleware.ts @@ -1,7 +1,7 @@ import { plainToInstance } from 'class-transformer'; import { validateOrReject, ValidationError } from 'class-validator'; import { NextFunction, Request, Response } from 'express'; -import { HttpException } from '@exceptions/httpException'; +import { HttpException } from '@exceptions/HttpException'; /** * @name ValidationMiddleware diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index b77a8dc..d66793a 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -1,17 +1,200 @@ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema +generator client { + provider = "prisma-client-js" +} + datasource db { - provider = "mysql" + provider = "postgresql" url = env("DATABASE_URL") } -generator client { - provider = "prisma-client-js" +model User { + id String @id @default(uuid()) + name String @db.VarChar(255) + email String @unique @db.VarChar(255) + username String @unique @db.VarChar(255) + phone String @db.VarChar(20) + password_hash String @db.VarChar(255) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + // Relations + patient Patient? + doctor Doctor? + appointments_as_patient Appointment[] @relation("PatientAppointments") + appointments_as_doctor Appointment[] @relation("DoctorAppointments") + medications_as_patient Medication[] @relation("PatientMedications") + medications_as_doctor Medication[] @relation("DoctorMedications") + scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") + clinics_as_nurse ClinicNurse[] + audit_logs AuditLog[] + controlled_patients Patient[] @relation("ControllingNurse") + + @@map("Users") } -model User { - id Int @id @default(autoincrement()) - email String @unique - password String +model Doctor { + id String @id @default(uuid()) + specification String @db.VarChar(255) + avg_time DateTime? @db.Time(0) + + // Relations + user User @relation(fields: [id], references: [id]) + clinic_doctors ClinicDoctor[] + + @@map("Doctor") +} + +model Patient { + id String @id @default(uuid()) + bc_address String @db.VarChar(255) + consent Boolean @default(false) + controlling_nurse String? + + // Relations + user User @relation(fields: [id], references: [id]) + controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse], references: [id]) + + @@map("Patient") +} + +model Appointment { + id String @id @default(uuid()) + patient_id String + doctor_id String + scheduled_time DateTime + is_online Boolean @default(false) + is_completed Boolean @default(false) + estimated_time Float? + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + // Relations + patient User @relation("PatientAppointments", fields: [patient_id], references: [id]) + doctor User @relation("DoctorAppointments", fields: [doctor_id], references: [id]) + + @@map("Appointments") +} + +model Medication { + id String @id @default(uuid()) + patient_id String + doctor_id String + treatment_name String @db.VarChar(255) + medication_end_date DateTime + medication_start_time DateTime @db.Time(0) + frequency Int + period Period + description String? @db.Text + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + // Relations + patient User @relation("PatientMedications", fields: [patient_id], references: [id]) + doctor User @relation("DoctorMedications", fields: [doctor_id], references: [id]) + + @@map("Medications") +} + +model ScanLab { + id String @id @default(uuid()) + patient_id String + doctor_id String + name String @db.VarChar(255) + scheduled_date DateTime? + scheduled_time DateTime? @db.Time(0) + frequency Int? + period Period? + description String? @db.Text + type ScanLabType + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + // Relations + patient User @relation("PatientScansLabs", fields: [patient_id], references: [id]) + doctor User @relation("DoctorScansLabs", fields: [doctor_id], references: [id]) + + @@map("Scans_Labs") +} + +model Clinic { + id String @id @default(uuid()) + is_active Boolean @default(true) + opening_at DateTime @db.Time(0) + closing_at DateTime @db.Time(0) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + // Relations + clinic_nurses ClinicNurse[] + clinic_doctors ClinicDoctor[] + + @@map("Clinic") } + +model ClinicNurse { + id String @id @default(uuid()) + clinic_id String + nurse_id String + + // Relations + clinic Clinic @relation(fields: [clinic_id], references: [id]) + nurse User @relation(fields: [nurse_id], references: [id]) + + @@map("ClinicNurse") +} + +model ClinicDoctor { + id String @id @default(uuid()) + clinic_id String + doctor_id String + + // Relations + clinic Clinic @relation(fields: [clinic_id], references: [id]) + doctor Doctor @relation(fields: [doctor_id], references: [id]) + + @@map("ClinicDoctor") +} + +model AuditLog { + id String @id @default(uuid()) + user_id String + action Action + bc_hash String @db.VarChar(255) + created_at DateTime @default(now()) + + // Relations + user User @relation(fields: [user_id], references: [id]) + + @@map("AuditLogs") +} + + +enum ScanLabType { + SCAN + LAB +} + +enum Action { + CREATE + UPDATE + DELETE + READ + LOGIN + LOGOUT +} + +enum Period { + DAILY + WEEKLY + MONTHLY + YEARLY +} \ No newline at end of file diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index f721645..4a43f9d 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -4,7 +4,7 @@ import { sign } from 'jsonwebtoken'; import { Service } from 'typedi'; import { SECRET_KEY } from '@config'; import { CreateUserDto } from '@dtos/users.dto'; -import { HttpException } from '@exceptions/httpException'; +import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, TokenData } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; @@ -27,7 +27,7 @@ export class AuthService { if (!findUser) throw new HttpException(409, `This email ${userData.email} was not found`); const isPasswordMatching: boolean = await compare(userData.password, findUser.password); - if (!isPasswordMatching) throw new HttpException(409, "Password is not matching"); + if (!isPasswordMatching) throw new HttpException(409, 'Password is not matching'); const tokenData = this.createToken(findUser); const cookie = this.createCookie(tokenData); diff --git a/src/services/users.service.ts b/src/services/users.service.ts index d027d7a..015e530 100644 --- a/src/services/users.service.ts +++ b/src/services/users.service.ts @@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; import { CreateUserDto } from '@dtos/users.dto'; -import { HttpException } from '@/exceptions/httpException'; +import { HttpException } from '@/exceptions/HttpException'; import { User } from '@interfaces/users.interface'; @Service() From ed28ba4b5a1f12dedf2407b6f519549162ef932a Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 26 Oct 2025 20:35:12 +0300 Subject: [PATCH 003/210] Added the interfaces required and edited the eslint config to be less strict --- .eslintrc | 3 +- src/interfaces/appointments.interface.ts | 17 ++++++++ src/interfaces/audit-logs.interface.ts | 12 ++++++ src/interfaces/clinics.interface.ts | 32 ++++++++++++++++ src/interfaces/enums.interface.ts | 20 ++++++++++ src/interfaces/index.ts | 26 +++++++++++++ src/interfaces/medications.interface.ts | 20 ++++++++++ src/interfaces/scans-labs.interface.ts | 21 ++++++++++ src/interfaces/users.interface.ts | 49 +++++++++++++++++++++++- src/prisma/schema.prisma | 6 +-- 10 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 src/interfaces/appointments.interface.ts create mode 100644 src/interfaces/audit-logs.interface.ts create mode 100644 src/interfaces/clinics.interface.ts create mode 100644 src/interfaces/enums.interface.ts create mode 100644 src/interfaces/index.ts create mode 100644 src/interfaces/medications.interface.ts create mode 100644 src/interfaces/scans-labs.interface.ts diff --git a/.eslintrc b/.eslintrc index 206ab05..e073993 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ "@typescript-eslint/explicit-module-boundary-types": 0, "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-types": "off", - "@typescript-eslint/no-var-requires": "off" + "@typescript-eslint/no-var-requires": "off", + "prettier/prettier": "off" } } diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts new file mode 100644 index 0000000..3ae1c06 --- /dev/null +++ b/src/interfaces/appointments.interface.ts @@ -0,0 +1,17 @@ +import { User } from './users.interface'; + +export interface Appointment { + id: string; + patient_id: string; + doctor_id: string; + scheduled_time: Date; + is_online: boolean; + is_completed: boolean; + estimated_time?: number; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + patient: User; + doctor: User; +} \ No newline at end of file diff --git a/src/interfaces/audit-logs.interface.ts b/src/interfaces/audit-logs.interface.ts new file mode 100644 index 0000000..0092c54 --- /dev/null +++ b/src/interfaces/audit-logs.interface.ts @@ -0,0 +1,12 @@ +import { User } from './users.interface'; +import { Action } from './enums.interface'; + +export interface AuditLog { + id: string; + user_id: string; + action: Action; + bc_hash: string; + created_at: Date; + + user: User; +} diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts new file mode 100644 index 0000000..48ef819 --- /dev/null +++ b/src/interfaces/clinics.interface.ts @@ -0,0 +1,32 @@ +import { User, Doctor } from './users.interface'; + +export interface Clinic { + id: string; + is_active: boolean; + opening_at: Date; + closing_at: Date; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + clinic_nurses?: ClinicNurse[]; + clinic_doctors?: ClinicDoctor[]; +} + +export interface ClinicNurse { + id: string; + clinic_id: string; + nurse_id: string; + + clinic: Clinic; + nurse: User; +} + +export interface ClinicDoctor { + id: string; + clinic_id: string; + doctor_id: string; + + clinic: Clinic; + doctor: Doctor; +} diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts new file mode 100644 index 0000000..bd853fb --- /dev/null +++ b/src/interfaces/enums.interface.ts @@ -0,0 +1,20 @@ +export enum Period { + DAILY = 'DAILY', + WEEKLY = 'WEEKLY', + MONTHLY = 'MONTHLY', + YEARLY = 'YEARLY', +} + +export enum ScanLabType { + SCAN = 'SCAN', + LAB = 'LAB', +} + +export enum Action { + CREATE = 'CREATE', + UPDATE = 'UPDATE', + DELETE = 'DELETE', + READ = 'READ', + LOGIN = 'LOGIN', + LOGOUT = 'LOGOUT', +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts new file mode 100644 index 0000000..9c4853b --- /dev/null +++ b/src/interfaces/index.ts @@ -0,0 +1,26 @@ +// Enums +export * from './enums.interface'; + +// Auth +export * from './auth.interface'; + +// Users +export * from './users.interface'; + +// Routes +export * from './routes.interface'; + +// Appointments +export * from './appointments.interface'; + +// Medications +export * from './medications.interface'; + +// Scans & Labs +export * from './scans-labs.interface'; + +// Clinics +export * from './clinics.interface'; + +// Audit Logs +export * from './audit-logs.interface'; diff --git a/src/interfaces/medications.interface.ts b/src/interfaces/medications.interface.ts new file mode 100644 index 0000000..f5bd0ad --- /dev/null +++ b/src/interfaces/medications.interface.ts @@ -0,0 +1,20 @@ +import { User } from './users.interface'; +import { Period } from './enums.interface'; + +export interface Medication { + id: string; + patient_id: string; + doctor_id: string; + treatment_name: string; + medication_end_date: Date; + medication_start_time: Date; + frequency: number; + period: Period; + description?: string; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + patient?: User; + doctor?: User; +} diff --git a/src/interfaces/scans-labs.interface.ts b/src/interfaces/scans-labs.interface.ts new file mode 100644 index 0000000..be17e8c --- /dev/null +++ b/src/interfaces/scans-labs.interface.ts @@ -0,0 +1,21 @@ +import { User } from './users.interface'; +import { Period, ScanLabType } from './enums.interface'; + +export interface ScanLab { + id: string; + patient_id: string; + doctor_id: string; + name: string; + scheduled_date?: Date; + scheduled_time?: Date; + frequency?: number; + period?: Period; + description?: string; + type: ScanLabType; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + patient?: User; + doctor?: User; +} diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 2f1dac4..223f268 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -1,5 +1,50 @@ +import { Appointment } from './appointments.interface'; +import { Medication } from './medications.interface'; +import { ScanLab } from './scans-labs.interface'; +import { ClinicNurse, ClinicDoctor } from './clinics.interface'; +import { AuditLog } from './audit-logs.interface'; + export interface User { - id?: number; + id: string; + name: string; email: string; - password: string; + username: string; + phone: string; + password_hash: string; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + patient?: Patient; + doctor?: Doctor; + appointments_as_patient?: Appointment[]; + appointments_as_doctor?: Appointment[]; + medications_as_patient?: Medication[]; + medications_as_doctor?: Medication[]; + scans_labs_as_patient?: ScanLab[]; + scans_labs_as_doctor?: ScanLab[]; + clinics_as_nurse?: ClinicNurse[]; + audit_logs?: AuditLog[]; + controlled_patients?: Patient[]; } + +export interface Patient { + id: string; + bc_address: string; + consent: boolean; + controlling_nurse_id?: string; + + user: User; + controlling_nurse_user?: User; +} + +export interface Doctor { + id: string; + specialization: string; + avg_time?: Date; + + user: User; + clinic_doctors?: ClinicDoctor[]; +} + + diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index d66793a..c20134b 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -39,7 +39,7 @@ model User { model Doctor { id String @id @default(uuid()) - specification String @db.VarChar(255) + specialization String @db.VarChar(255) avg_time DateTime? @db.Time(0) // Relations @@ -53,11 +53,11 @@ model Patient { id String @id @default(uuid()) bc_address String @db.VarChar(255) consent Boolean @default(false) - controlling_nurse String? + controlling_nurse_id String? // Relations user User @relation(fields: [id], references: [id]) - controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse], references: [id]) + controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id]) @@map("Patient") } From d502a0033cd4fa84cfd06b188e6ce47d9742fe2d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 26 Oct 2025 23:36:48 +0300 Subject: [PATCH 004/210] update docker-compose - Switched from mysql to pg - added Prisma migrate/generate - improved dependency handling (depends_on) --- docker-compose.yml | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9ab30f4..f940e33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,10 @@ services: ports: - "80:80" volumes: - - ./nginx.conf:/etc/nginx/nginx.conf + - ./nginx.conf:/etc/nginx/nginx.conf restart: "unless-stopped" + depends_on: + - server networks: - backend @@ -19,28 +21,38 @@ services: dockerfile: Dockerfile.dev ports: - "3000:3000" + - "5555:5555" environment: - DATABASE_URL: mysql://root:password@localhost:3306/dev + DATABASE_URL: "postgresql://myuser:mypassword@postgres:5432/mydatabase?schema=public" + NODE_ENV: development volumes: - ./:/app - /app/node_modules restart: "unless-stopped" + depends_on: + - postgres networks: - backend - links: - - mysql - depends_on: - - mysql + command: > + sh -c " + npx prisma migrate dev && + npx prisma generate && + npm run dev + " - mysql: - container_name: mysql - image: mysql:5.7 + postgres: + container_name: postgres_db + image: postgres:16 environment: - DATABASE_URL: mysql://root:password@localhost:3306/dev - ports: - - "3306:3306" + - POSTGRES_USER=myuser + - POSTGRES_PASSWORD=mypassword + - POSTGRES_DB=mydatabase + volumes: + - data:/var/lib/postgresql/data + restart: unless-stopped networks: - backend + networks: backend: From 779e36c88aaf7b6a6557561a35a5d9a0922e3fc5 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 28 Oct 2025 15:13:07 +0300 Subject: [PATCH 005/210] update docker setup / first migration --- Dockerfile.dev | 9 +- docker-compose.yml | 2 - .../20210314081925_initial/migration.sql | 9 - .../20251028110051_init/migration.sql | 185 ++++++++++++++++++ src/prisma/migrations/migration_lock.toml | 2 +- src/utils/logger.ts | 2 + 6 files changed, 195 insertions(+), 14 deletions(-) delete mode 100644 src/prisma/migrations/20210314081925_initial/migration.sql create mode 100644 src/prisma/migrations/20251028110051_init/migration.sql diff --git a/Dockerfile.dev b/Dockerfile.dev index f0c9f87..4e125d0 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,10 @@ # NodeJS Version 16 -FROM node:16.18-buster-slim +FROM node:16-bullseye + +# install openSSL (for prisma) +RUN apt-get update -y && \ + apt-get install -y openssl && \ + rm -rf /var/lib/apt/lists/* # Copy Dir COPY . ./app @@ -8,7 +13,7 @@ COPY . ./app WORKDIR /app # Install Node Package -RUN npm install --legacy-peer-deps +RUN npm install --legacy-peer-deps # Set Env ENV NODE_ENV development diff --git a/docker-compose.yml b/docker-compose.yml index f940e33..bff43a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.9" services: proxy: @@ -35,7 +34,6 @@ services: - backend command: > sh -c " - npx prisma migrate dev && npx prisma generate && npm run dev " diff --git a/src/prisma/migrations/20210314081925_initial/migration.sql b/src/prisma/migrations/20210314081925_initial/migration.sql deleted file mode 100644 index ca07fb7..0000000 --- a/src/prisma/migrations/20210314081925_initial/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- CreateTable -CREATE TABLE `User` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `email` VARCHAR(191) NOT NULL, - `password` VARCHAR(191) NOT NULL, -UNIQUE INDEX `User.email_unique`(`email`), - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/src/prisma/migrations/20251028110051_init/migration.sql b/src/prisma/migrations/20251028110051_init/migration.sql new file mode 100644 index 0000000..bdfa420 --- /dev/null +++ b/src/prisma/migrations/20251028110051_init/migration.sql @@ -0,0 +1,185 @@ +-- CreateEnum +CREATE TYPE "ScanLabType" AS ENUM ('SCAN', 'LAB'); + +-- CreateEnum +CREATE TYPE "Action" AS ENUM ('CREATE', 'UPDATE', 'DELETE', 'READ', 'LOGIN', 'LOGOUT'); + +-- CreateEnum +CREATE TYPE "Period" AS ENUM ('DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'); + +-- CreateTable +CREATE TABLE "Users" ( + "id" TEXT NOT NULL, + "name" VARCHAR(255) NOT NULL, + "email" VARCHAR(255) NOT NULL, + "username" VARCHAR(255) NOT NULL, + "phone" VARCHAR(20) NOT NULL, + "password_hash" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Doctor" ( + "id" TEXT NOT NULL, + "specialization" VARCHAR(255) NOT NULL, + "avg_time" TIME(0), + + CONSTRAINT "Doctor_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Patient" ( + "id" TEXT NOT NULL, + "bc_address" VARCHAR(255) NOT NULL, + "consent" BOOLEAN NOT NULL DEFAULT false, + "controlling_nurse_id" TEXT, + + CONSTRAINT "Patient_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Appointments" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "scheduled_time" TIMESTAMP(3) NOT NULL, + "is_online" BOOLEAN NOT NULL DEFAULT false, + "is_completed" BOOLEAN NOT NULL DEFAULT false, + "estimated_time" DOUBLE PRECISION, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Appointments_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Medications" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "treatment_name" VARCHAR(255) NOT NULL, + "medication_end_date" TIMESTAMP(3) NOT NULL, + "medication_start_time" TIME(0) NOT NULL, + "frequency" INTEGER NOT NULL, + "period" "Period" NOT NULL, + "description" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Medications_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Scans_Labs" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "name" VARCHAR(255) NOT NULL, + "scheduled_date" TIMESTAMP(3), + "scheduled_time" TIME(0), + "frequency" INTEGER, + "period" "Period", + "description" TEXT, + "type" "ScanLabType" NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Scans_Labs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Clinic" ( + "id" TEXT NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "opening_at" TIME(0) NOT NULL, + "closing_at" TIME(0) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Clinic_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ClinicNurse" ( + "id" TEXT NOT NULL, + "clinic_id" TEXT NOT NULL, + "nurse_id" TEXT NOT NULL, + + CONSTRAINT "ClinicNurse_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ClinicDoctor" ( + "id" TEXT NOT NULL, + "clinic_id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + + CONSTRAINT "ClinicDoctor_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AuditLogs" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "action" "Action" NOT NULL, + "bc_hash" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AuditLogs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Users_email_key" ON "Users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Users_username_key" ON "Users"("username"); + +-- AddForeignKey +ALTER TABLE "Doctor" ADD CONSTRAINT "Doctor_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Patient" ADD CONSTRAINT "Patient_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Patient" ADD CONSTRAINT "Patient_controlling_nurse_id_fkey" FOREIGN KEY ("controlling_nurse_id") REFERENCES "Users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Appointments" ADD CONSTRAINT "Appointments_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Appointments" ADD CONSTRAINT "Appointments_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Medications" ADD CONSTRAINT "Medications_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Medications" ADD CONSTRAINT "Medications_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Scans_Labs" ADD CONSTRAINT "Scans_Labs_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Scans_Labs" ADD CONSTRAINT "Scans_Labs_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_nurse_id_fkey" FOREIGN KEY ("nurse_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuditLogs" ADD CONSTRAINT "AuditLogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/migrations/migration_lock.toml b/src/prisma/migrations/migration_lock.toml index e5a788a..fbffa92 100644 --- a/src/prisma/migrations/migration_lock.toml +++ b/src/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "mysql" \ No newline at end of file +provider = "postgresql" \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts index b8f43a9..12829d3 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -4,6 +4,8 @@ import winston from 'winston'; import winstonDaily from 'winston-daily-rotate-file'; import { LOG_DIR } from '@config'; + + // logs dir const logDir: string = join(__dirname, LOG_DIR); From 44904cbcd36a4e3c41748ab1da32102c4947f716 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 30 Oct 2025 00:49:23 +0300 Subject: [PATCH 006/210] dockerfile modifications, CI/CD --- .github/workflows/ci.yml | 58 +++++++++++++++++++ .vscode/settings.json | 5 +- Dockerfile.dev | 39 +++++++++---- docker-compose.yml | 7 +-- .../migration.sql | 0 5 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/ci.yml rename src/prisma/migrations/{20251028110051_init => 20251029183425_init}/migration.sql (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9ba2b9b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: Build and Deploy Blockchain-Based EMR System + +on: + push: + branches: + - main + - dev + + pull_request: + branches: + - main + - dev + +jobs: + tests: + runs-on: ubuntu-latest + + env: + NODE_ENV: test + DATABASE_URL: "postgresql://testuser:testpassword@localhost:5432/testdb" + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpassword + POSTGRES_DB: testdb + ports: + - 5432:5432 + + steps: + - name: checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: install dependencies + run: npm ci + + - name: generate prisma client + run: npx prisma generate + + - name: run Prisma migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ env.DATABASE_URL }} + + - name: build app + run: npm run build + + - name: run tests + run: npm test + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 70abc46..c5e5d42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, - "editor.formatOnSave": false + "editor.formatOnSave": false, + "yaml.schemas": { + "https://www.schemastore.org/github-workflow.json": "file:///home/enjy/work/GP/repo/Backend/.github/workflows/ci.yml" + } } diff --git a/Dockerfile.dev b/Dockerfile.dev index 4e125d0..07ac86d 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,22 +1,39 @@ -# NodeJS Version 16 -FROM node:16-bullseye +# First stage: BUILD THE APP # -# install openSSL (for prisma) -RUN apt-get update -y && \ - apt-get install -y openssl && \ - rm -rf /var/lib/apt/lists/* - -# Copy Dir -COPY . ./app +# NodeJS Version 22 +FROM node:22-bullseye AS builder # Work to Dir WORKDIR /app +#copy package and prisma files +COPY package*.json ./ +COPY src/prisma ./src/prisma + # Install Node Package -RUN npm install --legacy-peer-deps +RUN npm ci --legacy-peer-deps + +# generate prisma client +RUN npx prisma generate + +# Copy rest of the app +COPY . . + + + +# second stage # + +FROM node:22-bullseye AS runner + +WORKDIR /app + +# copy only needed files +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/src/prisma ./src/prisma +COPY --from=builder /app/package*.json ./ # Set Env -ENV NODE_ENV development +ENV NODE_ENV=development EXPOSE 3000 diff --git a/docker-compose.yml b/docker-compose.yml index bff43a5..a60e6ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,11 +32,8 @@ services: - postgres networks: - backend - command: > - sh -c " - npx prisma generate && - npm run dev - " + command: npm run dev + postgres: container_name: postgres_db diff --git a/src/prisma/migrations/20251028110051_init/migration.sql b/src/prisma/migrations/20251029183425_init/migration.sql similarity index 100% rename from src/prisma/migrations/20251028110051_init/migration.sql rename to src/prisma/migrations/20251029183425_init/migration.sql From a21980a336d2b4c0b7006db87178738c80b20a61 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 31 Oct 2025 17:20:24 +0200 Subject: [PATCH 007/210] edited some logic in the auth services and updated the prisma schema --- src/controllers/auth.controller.ts | 11 ++++++----- src/dtos/users.dto.ts | 19 ++++++++++++++++++- src/interfaces/auth.interface.ts | 2 +- src/middlewares/auth.middleware.ts | 3 ++- src/prisma/schema.prisma | 9 +++++++++ src/routes/auth.route.ts | 10 +++++----- src/services/auth.service.ts | 20 +++++++++++++------- 7 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index ab035db..cc5a9e1 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -3,16 +3,17 @@ import { Container } from 'typedi'; import { RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { AuthService } from '@services/auth.service'; +import { CreateUserDto, LoginUserDto } from '@/dtos/users.dto'; export class AuthController { public auth = Container.get(AuthService); public signUp = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const userData: User = req.body; + const userData: CreateUserDto = req.body; const signUpUserData: User = await this.auth.signup(userData); - res.status(201).json({ data: signUpUserData, message: 'signup' }); + res.status(201).json({ data: signUpUserData, message: 'Signed Up Successfully' }); } catch (error) { next(error); } @@ -20,11 +21,11 @@ export class AuthController { public logIn = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const userData: User = req.body; + const userData: LoginUserDto = req.body; const { cookie, findUser } = await this.auth.login(userData); res.setHeader('Set-Cookie', [cookie]); - res.status(200).json({ data: findUser, message: 'login' }); + res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); } catch (error) { next(error); } @@ -36,7 +37,7 @@ export class AuthController { const logOutUserData: User = await this.auth.logout(userData); res.setHeader('Set-Cookie', ['Authorization=; Max-age=0']); - res.status(200).json({ data: logOutUserData, message: 'logout' }); + res.status(200).json({ message: 'Logged Out Successfully' }); } catch (error) { next(error); } diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 5bf903b..e1f6077 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -6,11 +6,28 @@ export class CreateUserDto { @IsString() @IsNotEmpty() - @MinLength(9) + public name: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsString() + @IsNotEmpty() + @MinLength(8) @MaxLength(32) public password: string; } +export class LoginUserDto { + @IsEmail() + public email: string; + + @IsString() + @IsNotEmpty() + public password: string; +} + export class UpdateUserDto { @IsString() @IsNotEmpty() diff --git a/src/interfaces/auth.interface.ts b/src/interfaces/auth.interface.ts index 83947b8..0e9335b 100644 --- a/src/interfaces/auth.interface.ts +++ b/src/interfaces/auth.interface.ts @@ -2,7 +2,7 @@ import { Request } from 'express'; import { User } from '@interfaces/users.interface'; export interface DataStoredInToken { - id: number; + id: string; } export interface TokenData { diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 113af89..1523915 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -4,6 +4,7 @@ import { verify } from 'jsonwebtoken'; import { SECRET_KEY } from '@config'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, RequestWithUser } from '@interfaces/auth.interface'; +import { User } from '@/interfaces'; const getAuthorization = (req: Request) => { const cookie = req.cookies['Authorization']; @@ -22,7 +23,7 @@ export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: if (Authorization) { const { id } = (await verify(Authorization, SECRET_KEY)) as DataStoredInToken; const users = new PrismaClient().user; - const findUser = await users.findUnique({ where: { id: Number(id) } }); + const findUser: User = await users.findUnique({ where: { id } }); if (findUser) { req.user = findUser; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index c20134b..ef9193b 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -17,6 +17,8 @@ model User { username String @unique @db.VarChar(255) phone String @db.VarChar(20) password_hash String @db.VarChar(255) + gender Gender + date_of_birth DateTime created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? @@ -86,6 +88,7 @@ model Medication { patient_id String doctor_id String treatment_name String @db.VarChar(255) + category String @db.VarChar(100) medication_end_date DateTime medication_start_time DateTime @db.Time(0) frequency Int @@ -129,6 +132,7 @@ model Clinic { is_active Boolean @default(true) opening_at DateTime @db.Time(0) closing_at DateTime @db.Time(0) + address String @db.VarChar(300) created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? @@ -197,4 +201,9 @@ enum Period { WEEKLY MONTHLY YEARLY +} + +enum Gender { + MALE + FEMALE } \ No newline at end of file diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index a32969f..ecfe266 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -1,12 +1,12 @@ import { Router } from 'express'; import { AuthController } from '@controllers/auth.controller'; -import { CreateUserDto } from '@dtos/users.dto'; +import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { ValidationMiddleware } from '@middlewares/validation.middleware'; export class AuthRoute implements Routes { - public path = '/'; + public path = '/auth'; public router = Router(); public auth = new AuthController(); @@ -15,8 +15,8 @@ export class AuthRoute implements Routes { } private initializeRoutes() { - this.router.post(`${this.path}signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); - this.router.post(`${this.path}login`, ValidationMiddleware(CreateUserDto), this.auth.logIn); - this.router.post(`${this.path}logout`, AuthMiddleware, this.auth.logOut); + this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); + this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); + this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 4a43f9d..d042d23 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -3,7 +3,7 @@ import { compare, hash } from 'bcrypt'; import { sign } from 'jsonwebtoken'; import { Service } from 'typedi'; import { SECRET_KEY } from '@config'; -import { CreateUserDto } from '@dtos/users.dto'; +import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, TokenData } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; @@ -13,20 +13,26 @@ export class AuthService { public users = new PrismaClient().user; public async signup(userData: CreateUserDto): Promise { - const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); - if (findUser) throw new HttpException(409, `This email ${userData.email} already exists`); + const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); + if (findUserSameEmail) throw new HttpException(409, `This email ${userData.email} already exists`); + + const emailHandle = userData.email.split('@')[0]; + const findUserSameUsername: User = await this.users.findUnique({ where: { username: emailHandle } }); + if (findUserSameUsername) throw new HttpException(409, `This username ${emailHandle} already exists`); const hashedPassword = await hash(userData.password, 10); - const createUserData: Promise = this.users.create({ data: { ...userData, password: hashedPassword } }); + const username = emailHandle; + + const createUserData: Promise = this.users.create({ data: { ...userData, username ,password_hash: hashedPassword } }); return createUserData; } - public async login(userData: CreateUserDto): Promise<{ cookie: string; findUser: User }> { + public async login(userData: LoginUserDto): Promise<{ cookie: string; findUser: User }> { const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); if (!findUser) throw new HttpException(409, `This email ${userData.email} was not found`); - const isPasswordMatching: boolean = await compare(userData.password, findUser.password); + const isPasswordMatching: boolean = await compare(userData.password, findUser.password_hash); if (!isPasswordMatching) throw new HttpException(409, 'Password is not matching'); const tokenData = this.createToken(findUser); @@ -36,7 +42,7 @@ export class AuthService { } public async logout(userData: User): Promise { - const findUser: User = await this.users.findFirst({ where: { email: userData.email, password: userData.password } }); + const findUser: User = await this.users.findFirst({ where: { email: userData.email, password_hash: userData.password_hash } }); if (!findUser) throw new HttpException(409, "User doesn't exist"); return findUser; From 70febb04a558b7e376cc25fbdde0501bdd21a5e0 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 31 Oct 2025 20:19:12 +0200 Subject: [PATCH 008/210] updated the installed packages and edited schema relations --- package-lock.json | 7690 ++++++++++------- package.json | 106 +- src/dtos/users.dto.ts | 13 +- src/interfaces/clinics.interface.ts | 1 + src/interfaces/medications.interface.ts | 1 + src/interfaces/users.interface.ts | 4 +- .../migration.sql | 62 +- src/prisma/migrations/migration_lock.toml | 4 +- src/prisma/schema.prisma | 53 +- src/server.ts | 2 +- src/services/auth.service.ts | 4 +- 11 files changed, 4526 insertions(+), 3414 deletions(-) rename src/prisma/migrations/{20251029183425_init => 20251031175320_init_schema}/migration.sql (77%) diff --git a/package-lock.json b/package-lock.json index 3cbe10c..48d31db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,64 +9,66 @@ "version": "0.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^4.1.0", - "bcrypt": "^5.0.1", + "@prisma/client": "^6.18.0", + "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", - "class-validator": "^0.13.2", - "compression": "^1.7.4", - "cookie-parser": "^1.4.6", + "class-validator": "^0.14.2", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", - "dotenv": "^16.0.1", - "envalid": "^7.3.1", - "express": "^4.18.1", - "helmet": "^5.1.1", + "dotenv": "^17.2.3", + "envalid": "^8.1.0", + "express": "^5.1.0", + "helmet": "^8.1.0", "hpp": "^0.2.3", - "jsonwebtoken": "^8.5.1", - "morgan": "^1.10.0", - "reflect-metadata": "^0.1.13", - "swagger-jsdoc": "^6.2.1", - "swagger-ui-express": "^4.5.0", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.1", + "npm-check-updates": "^19.1.2", + "reflect-metadata": "^0.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", - "winston": "^3.8.1", - "winston-daily-rotate-file": "^4.7.1" + "winston": "^3.18.3", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { - "@swc/cli": "^0.1.57", - "@swc/core": "^1.2.220", - "@types/bcrypt": "^5.0.0", - "@types/compression": "^1.7.2", - "@types/cookie-parser": "^1.4.3", - "@types/cors": "^2.8.12", - "@types/express": "^4.17.13", - "@types/hpp": "^0.2.2", - "@types/jest": "^28.1.6", - "@types/jsonwebtoken": "^8.5.8", - "@types/morgan": "^1.9.3", - "@types/node": "^17.0.45", - "@types/supertest": "^2.0.12", - "@types/swagger-jsdoc": "^6.0.1", - "@types/swagger-ui-express": "^4.1.3", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", - "cross-env": "^7.0.3", - "eslint": "^8.20.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.2.1", - "husky": "^8.0.1", - "jest": "^28.1.1", - "lint-staged": "^13.0.3", + "@swc/cli": "^0.7.8", + "@swc/core": "^1.14.0", + "@types/bcrypt": "^6.0.0", + "@types/compression": "^1.8.1", + "@types/cookie-parser": "^1.4.10", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/hpp": "^0.2.7", + "@types/jest": "^30.0.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/morgan": "^1.9.10", + "@types/node": "^24.9.2", + "@types/supertest": "^6.0.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "@typescript-eslint/eslint-plugin": "^8.46.2", + "@typescript-eslint/parser": "^8.46.2", + "cross-env": "^10.1.0", + "dotenv-cli": "^11.0.0", + "eslint": "^9.38.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "husky": "^9.1.7", + "jest": "^30.2.0", + "lint-staged": "^16.2.6", "node-config": "^0.0.2", - "node-gyp": "^9.1.0", - "nodemon": "^2.0.19", - "pm2": "^5.2.0", - "prettier": "^2.7.1", - "prisma": "^4.1.0", - "supertest": "^6.2.4", - "ts-jest": "^28.0.7", - "ts-node": "^10.9.1", - "tsc-alias": "^1.7.0", - "tsconfig-paths": "^4.0.0", - "typescript": "^4.7.4" + "node-gyp": "^11.5.0", + "nodemon": "^3.1.10", + "pm2": "^6.0.13", + "prettier": "^3.6.2", + "prisma": "^6.18.0", + "supertest": "^7.1.4", + "ts-jest": "^29.4.5", + "ts-node": "^10.9.2", + "tsc-alias": "^1.8.16", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.3" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -162,12 +164,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "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 - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -411,6 +407,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -579,6 +590,16 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -619,6 +640,43 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", + "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -646,16 +704,88 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -663,7 +793,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -692,55 +822,71 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -756,12 +902,47 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -878,60 +1059,59 @@ } }, "node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -942,111 +1122,141 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^28.1.3" + "jest-mock": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, "dependencies": { - "jest-get-type": "^28.0.2" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" + "@types/node": "*", + "jest-regex-util": "30.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1058,102 +1268,118 @@ } }, "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "dependencies": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1206,131 +1432,439 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, - "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/@mole-inc/bin-wrapper": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz", - "integrity": "sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==", + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, - "dependencies": { - "bin-check": "^4.1.0", - "bin-version-check": "^5.0.0", - "content-disposition": "^0.5.4", - "ext-name": "^5.0.0", - "file-type": "^17.1.6", - "filenamify": "^5.0.2", - "got": "^11.8.5", - "os-filter-obj": "^2.0.0" + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 10" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">= 10" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 8" + "node": ">= 10" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 8" + "node": ">= 10" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" }, "engines": { "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "semver": "^7.3.5" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "dependencies": { "@noble/hashes": "^1.1.5" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@pm2/agent": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.4.tgz", - "integrity": "sha512-n7WYvvTJhHLS2oBb1PjOtgLpMhgImOq8sXkPBw6smeg9LJBWZjiEgPKOpR8mn9UJZsB5P3W4V/MyvNnp31LKeA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", + "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", "dev": true, "dependencies": { "async": "~3.2.0", @@ -1338,12 +1872,11 @@ "dayjs": "~1.8.24", "debug": "~4.3.1", "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.0.0-1", + "fast-json-patch": "^3.1.0", "fclone": "~1.0.11", - "nssocket": "0.6.0", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.3.0", + "proxy-agent": "~6.4.0", "semver": "~7.5.0", "ws": "~7.5.10" } @@ -1417,10 +1950,22 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/@pm2/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", + "dev": true, + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/@pm2/io": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.0.1.tgz", - "integrity": "sha512-KiA+shC6sULQAr9mGZ1pg+6KVW9MF8NpG99x26Lf/082/Qy8qsTCtnJy+HQReW1A9Rdf0C/404cz0RZGZro+IA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", + "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", "dev": true, "dependencies": { "async": "~2.6.1", @@ -1565,36 +2110,82 @@ } }, "node_modules/@prisma/client": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", - "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, - "dependencies": { - "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" - }, "engines": { - "node": ">=14.17" + "node": ">=18.18" }, "peerDependencies": { - "prisma": "*" + "prisma": "*", + "typescript": ">=5.1.0" }, "peerDependenciesMeta": { "prisma": { "optional": true + }, + "typescript": { + "optional": true } } }, + "node_modules/@prisma/config": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "devOptional": true, + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", + "devOptional": true + }, "node_modules/@prisma/engines": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz", - "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==", - "dev": true, - "hasInstallScript": true + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" + } }, "node_modules/@prisma/engines-version": { - "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", - "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.18.0" + } }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -1603,39 +2194,39 @@ "hasInstallScript": true }, "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true }, "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@so-ric/colorspace": { @@ -1647,19 +2238,27 @@ "text-hex": "1.0.x" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true + }, "node_modules/@swc/cli": { - "version": "0.1.65", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.65.tgz", - "integrity": "sha512-4NcgsvJVHhA7trDnMmkGLLvWMHu2kSy+qHx6QwRhhJhdiYdNUrhdp+ERxen73sYtaeEOYeLJcWrQ60nzKi6rpg==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", + "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, "dependencies": { - "@mole-inc/bin-wrapper": "^8.0.1", - "commander": "^7.1.0", - "fast-glob": "^3.2.5", + "@swc/counter": "^0.1.3", + "@xhmikosr/bin-wrapper": "^13.0.5", + "commander": "^8.3.0", "minimatch": "^9.0.3", + "piscina": "^4.3.1", "semver": "^7.3.8", "slash": "3.0.0", - "source-map": "^0.7.3" + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" }, "bin": { "spack": "bin/spack.js", @@ -1667,11 +2266,11 @@ "swcx": "bin/swcx.js" }, "engines": { - "node": ">= 12.13" + "node": ">= 16.14.0" }, "peerDependencies": { "@swc/core": "^1.2.66", - "chokidar": "^3.5.1" + "chokidar": "^4.0.1" }, "peerDependenciesMeta": { "chokidar": { @@ -1680,14 +2279,14 @@ } }, "node_modules/@swc/core": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", - "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz", + "integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==", "dev": true, "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.24" + "@swc/types": "^0.1.25" }, "engines": { "node": ">=10" @@ -1697,16 +2296,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.5", - "@swc/core-darwin-x64": "1.13.5", - "@swc/core-linux-arm-gnueabihf": "1.13.5", - "@swc/core-linux-arm64-gnu": "1.13.5", - "@swc/core-linux-arm64-musl": "1.13.5", - "@swc/core-linux-x64-gnu": "1.13.5", - "@swc/core-linux-x64-musl": "1.13.5", - "@swc/core-win32-arm64-msvc": "1.13.5", - "@swc/core-win32-ia32-msvc": "1.13.5", - "@swc/core-win32-x64-msvc": "1.13.5" + "@swc/core-darwin-arm64": "1.14.0", + "@swc/core-darwin-x64": "1.14.0", + "@swc/core-linux-arm-gnueabihf": "1.14.0", + "@swc/core-linux-arm64-gnu": "1.14.0", + "@swc/core-linux-arm64-musl": "1.14.0", + "@swc/core-linux-x64-gnu": "1.14.0", + "@swc/core-linux-x64-musl": "1.14.0", + "@swc/core-win32-arm64-msvc": "1.14.0", + "@swc/core-win32-ia32-msvc": "1.14.0", + "@swc/core-win32-x64-msvc": "1.14.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1718,9 +2317,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", - "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.14.0.tgz", + "integrity": "sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==", "cpu": [ "arm64" ], @@ -1734,9 +2333,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", - "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.14.0.tgz", + "integrity": "sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==", "cpu": [ "x64" ], @@ -1750,9 +2349,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", - "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.14.0.tgz", + "integrity": "sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==", "cpu": [ "arm" ], @@ -1766,9 +2365,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", - "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.14.0.tgz", + "integrity": "sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==", "cpu": [ "arm64" ], @@ -1782,9 +2381,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", - "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.14.0.tgz", + "integrity": "sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==", "cpu": [ "arm64" ], @@ -1798,9 +2397,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", - "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.14.0.tgz", + "integrity": "sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==", "cpu": [ "x64" ], @@ -1814,9 +2413,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", - "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.14.0.tgz", + "integrity": "sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==", "cpu": [ "x64" ], @@ -1830,9 +2429,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", - "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.14.0.tgz", + "integrity": "sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==", "cpu": [ "arm64" ], @@ -1846,9 +2445,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", - "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.14.0.tgz", + "integrity": "sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==", "cpu": [ "ia32" ], @@ -1862,9 +2461,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", - "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.14.0.tgz", + "integrity": "sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==", "cpu": [ "x64" ], @@ -1893,15 +2492,33 @@ } }, "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, "node_modules/@tokenizer/token": { @@ -1910,15 +2527,6 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "dev": true }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -1949,6 +2557,16 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1991,9 +2609,9 @@ } }, "node_modules/@types/bcrypt": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", - "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2009,18 +2627,6 @@ "@types/node": "*" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/compression": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", @@ -2041,9 +2647,9 @@ } }, "node_modules/@types/cookie-parser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", - "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, "peerDependencies": { "@types/express": "*" @@ -2064,22 +2670,27 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "dependencies": { "@types/node": "*", @@ -2088,19 +2699,10 @@ "@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, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/hpp": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.6.tgz", - "integrity": "sha512-6gn1RuHA1/XFCVCqCkSV+AWy07YwtGg4re4SHhLMoiARTg9XlrbYMtVR+Uvws0VlERXzzcA+1UYvxEV6O+sgPg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.7.tgz", + "integrity": "sha512-YSQBkTwZepklRez0wgsljeewMytGNKgBAZR1YbmE0X49+elqkZ+fr/gvB407wL9Dl7a/Kv3W04yJueRmEHytBw==", "dev": true, "dependencies": { "@types/express": "*" @@ -2143,13 +2745,13 @@ } }, "node_modules/@types/jest": { - "version": "28.1.8", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", - "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "dependencies": { - "expect": "^28.0.0", - "pretty-format": "^28.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, "node_modules/@types/json-schema": { @@ -2158,20 +2760,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "dependencies": { + "@types/ms": "*", "@types/node": "*" } }, @@ -2196,17 +2790,20 @@ "@types/node": "*" } }, - "node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true + "node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/qs": { "version": "6.14.0", @@ -2220,25 +2817,10 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true - }, "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2284,12 +2866,13 @@ } }, "node_modules/@types/supertest": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", - "integrity": "sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "dependencies": { - "@types/superagent": "*" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, "node_modules/@types/swagger-jsdoc": { @@ -2313,10 +2896,15 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/validator": { + "version": "13.15.4", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.4.tgz", + "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==" + }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -2329,117 +2917,152 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2447,114 +3070,524 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true }, - "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/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] }, - "node_modules/abort-controller": { + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@xhmikosr/archive-type": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", + "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", + "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "isexe": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", + "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", + "dev": true, + "dependencies": { + "@xhmikosr/bin-check": "^7.1.0", + "@xhmikosr/downloader": "^15.2.0", + "@xhmikosr/os-filter-obj": "^3.0.0", + "bin-version-check": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", + "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.1.0", + "@xhmikosr/decompress-tarbz2": "^8.1.0", + "@xhmikosr/decompress-targz": "^8.1.0", + "@xhmikosr/decompress-unzip": "^7.1.0", + "graceful-fs": "^4.2.11", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", + "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", + "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", + "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", + "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0", + "get-stream": "^6.0.1", + "yauzl": "^3.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", + "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", + "dev": true, + "dependencies": { + "@xhmikosr/archive-type": "^7.1.0", + "@xhmikosr/decompress": "^10.2.0", + "content-disposition": "^0.5.4", + "defaults": "^2.0.2", + "ext-name": "^5.0.0", + "file-type": "^20.5.0", + "filenamify": "^6.0.0", + "get-stream": "^6.0.1", + "got": "^13.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/os-filter-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", "dev": true, "dependencies": { - "event-target-shim": "^5.0.0" + "arch": "^3.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=6.5" + "node": ">= 0.6" } }, - "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==", + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/accepts/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==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "engines": { "node": ">= 0.6" } @@ -2593,39 +3626,12 @@ } }, "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/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 14" } }, "node_modules/ajv": { @@ -2683,24 +3689,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -2718,6 +3716,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2731,15 +3738,10 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==" - }, "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", "dev": true, "funding": [ { @@ -2756,19 +3758,6 @@ } ] }, - "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/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2780,11 +3769,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "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==" - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2823,56 +3807,67 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "dependencies": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -2902,19 +3897,19 @@ } }, "node_modules/babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -2922,6 +3917,20 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.1.tgz", + "integrity": "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==", + "dev": true, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2943,9 +3952,9 @@ ] }, "node_modules/baseline-browser-mapping": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", - "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "version": "2.8.22", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", + "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==", "dev": true, "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -2977,29 +3986,16 @@ } }, "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/bin-check": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", - "dev": true, - "dependencies": { - "execa": "^0.7.0", - "executable": "^4.1.0" + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" }, "engines": { - "node": ">=4" + "node": ">= 18" } }, "node_modules/bin-version": { @@ -3035,65 +4031,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bin-version/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/bin-version/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bin-version/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bin-version/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3106,18 +4043,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/blessed": { - "version": "0.1.81", - "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", - "dev": true, - "bin": { - "blessed": "bin/tput.js" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/bodec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", @@ -3125,40 +4050,66 @@ "dev": true }, "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "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" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" } }, - "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==", + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "ms": "2.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.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==" + "node_modules/body-parser/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/brace-expansion": { "version": "2.0.2", @@ -3236,9 +4187,9 @@ } }, "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -3256,7 +4207,16 @@ ], "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" } }, "node_modules/buffer-equal-constant-time": { @@ -3278,116 +4238,100 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" + "url": "https://dotenvx.com" } }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">=14.16" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.16" } }, "node_modules/call-bind-apply-helpers": { @@ -3441,9 +4385,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001752", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", + "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", "dev": true, "funding": [ { @@ -3492,53 +4436,33 @@ "dev": true }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -3550,10 +4474,19 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", + "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", "dev": true }, "node_modules/class-transformer": { @@ -3562,33 +4495,25 @@ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "node_modules/class-validator": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", - "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "dependencies": { - "libphonenumber-js": "^1.9.43", - "validator": "^13.7.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" } }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3620,16 +4545,32 @@ } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3649,6 +4590,15 @@ "node": ">=12" } }, + "node_modules/cliui/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3678,6 +4628,18 @@ "node": ">=8" } }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3695,18 +4657,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3772,14 +4722,6 @@ "node": ">=12.20" } }, - "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/color/node_modules/color-convert": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", @@ -3818,12 +4760,12 @@ } }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/component-emitter": { @@ -3881,15 +4823,26 @@ "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/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "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==", + "dev": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -3906,9 +4859,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "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 }, "node_modules/cookie": { @@ -3967,21 +4920,20 @@ "dev": true }, "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=20" } }, "node_modules/cross-spawn": { @@ -4014,9 +4966,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", - "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", "dev": true }, "node_modules/debug": { @@ -4063,10 +5015,18 @@ } }, "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "version": "0.1.4", @@ -4083,6 +5043,27 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", + "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -4092,6 +5073,12 @@ "node": ">=10" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true + }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", @@ -4115,11 +5102,6 @@ "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4128,22 +5110,11 @@ "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==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "engines": { - "node": ">=8" - } + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true }, "node_modules/detect-newline": { "version": "3.1.0", @@ -4173,15 +5144,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4206,9 +5168,51 @@ } }, "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", + "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^17.1.0", + "dotenv-expand": "^12.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, "engines": { "node": ">=12" }, @@ -4248,16 +5252,26 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.240", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", - "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "dev": true }, "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "engines": { "node": ">=12" @@ -4272,6 +5286,15 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", @@ -4308,15 +5331,6 @@ "node": ">=0.10.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -4339,14 +5353,26 @@ } }, "node_modules/envalid": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/envalid/-/envalid-7.3.1.tgz", - "integrity": "sha512-KL1YRwn8WcoF/Ty7t+yLLtZol01xr9ZJMTjzoGRM8NaSU+nQQjSWOQKKJhJP2P57bpdakJ9jbxqQX4fGTOicZg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.0.tgz", + "integrity": "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==", "dependencies": { - "tslib": "2.3.1" + "tslib": "2.8.1" }, "engines": { - "node": ">=8.12" + "node": ">=18" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/err-code": { @@ -4453,15 +5479,6 @@ "source-map": "~0.6.1" } }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/escodegen/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4473,105 +5490,123 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.38.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", - "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz", - "integrity": "sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -4596,31 +5631,18 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4634,17 +5656,29 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4675,15 +5709,6 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4696,7 +5721,7 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", @@ -4705,15 +5730,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4730,15 +5746,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter2": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", @@ -4751,128 +5758,62 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", - "dev": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/execa/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "bare-events": "^2.7.0" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/execa/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "dependencies": { - "pify": "^2.2.0" + "node": ">=10" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/exponential-backoff": { @@ -4882,70 +5823,102 @@ "dev": true }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "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.12", - "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" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "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==", + "node_modules/express/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, "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==", + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" } }, - "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==" + "node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "devOptional": true }, "node_modules/ext-list": { "version": "2.2.2", @@ -4981,6 +5954,44 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-check/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4993,6 +6004,12 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -5074,16 +6091,22 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true + }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-stream-rotator": { @@ -5095,17 +6118,18 @@ } }, "node_modules/file-type": { - "version": "17.1.6", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.6.tgz", - "integrity": "sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", "dev": true, "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0-alpha.9", - "token-types": "^5.0.0-alpha.2" + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -5124,17 +6148,15 @@ } }, "node_modules/filenamify": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-5.1.1.tgz", - "integrity": "sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "dependencies": { - "filename-reserved-regex": "^3.0.0", - "strip-outer": "^2.0.0", - "trim-repeated": "^2.0.0" + "filename-reserved-regex": "^3.0.0" }, "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5153,35 +6175,21 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "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" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "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==", - "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==" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5214,17 +6222,16 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -5258,6 +6265,34 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -5274,16 +6309,27 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, "node_modules/formidable": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", - "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", - "once": "^1.4.0", - "qs": "^6.11.0" + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" @@ -5298,22 +6344,23 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "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==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, "dependencies": { - "minipass": "^3.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/fs.realpath": { @@ -5343,52 +6390,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/gauge/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/gauge/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/gauge/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/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5407,6 +6408,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5452,12 +6465,15 @@ } }, "node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-tsconfig": { @@ -5486,6 +6502,23 @@ "node": ">= 14" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/git-node-fs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", @@ -5499,20 +6532,20 @@ "dev": true }, "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", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "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" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5530,36 +6563,13 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/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/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5597,25 +6607,25 @@ } }, "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=10.19.0" + "node": ">=16" }, "funding": { "url": "https://github.com/sindresorhus/got?sponsor=1" @@ -5633,6 +6643,36 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5668,11 +6708,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5685,11 +6720,11 @@ } }, "node_modules/helmet": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.1.tgz", - "integrity": "sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/hpp": { @@ -5731,43 +6766,51 @@ "node": ">= 0.8" } }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "resolve-alpn": "^1.2.0" }, "engines": { "node": ">=10.19.0" } }, "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==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -5779,25 +6822,16 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -5807,6 +6841,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -5893,21 +6928,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5929,6 +6949,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + } + }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -5989,12 +7018,15 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6021,12 +7053,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6036,15 +7062,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6054,13 +7071,20 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isexe": { @@ -6079,28 +7103,19 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "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, "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": "^6.3.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/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, - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/istanbul-lib-report": { @@ -6117,44 +7132,20 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/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, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -6168,158 +7159,114 @@ "node": ">=8" } }, - "node_modules/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "@isaacs/cliui": "^8.0.2" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" }, - "engines": { - "node": ">=10" + "bin": { + "jest": "bin/jest.js" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "dependencies": { - "path-key": "^3.0.0" + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -6331,203 +7278,201 @@ } }, "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.2.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", "walker": "^1.0.8" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -6548,237 +7493,188 @@ } }, "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "dependencies": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^28.1.3" + "pretty-format": "30.2.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -6794,36 +7690,38 @@ } }, "node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "dependencies": { "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -6841,6 +7739,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-git": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", @@ -6926,9 +7833,9 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -6939,19 +7846,11 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jwa": { @@ -6982,13 +7881,13 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, "node_modules/kuler": { @@ -6996,15 +7895,6 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "node_modules/lazy": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", - "integrity": "sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7032,15 +7922,6 @@ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz", "integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==" }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7048,234 +7929,105 @@ "dev": true }, "node_modules/lint-staged": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", - "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.0.0", - "debug": "4.3.4", - "execa": "7.2.0", - "lilconfig": "2.1.0", - "listr2": "6.6.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.1" + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", + "dev": true, + "dependencies": { + "commander": "^14.0.1", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=20.17" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/lint-staged/node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/lint-staged/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/lint-staged/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "dev": true, "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20" } }, - "node_modules/lint-staged/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=8.6" + "node": ">=20.0.0" } }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/lint-staged/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/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { - "path-key": "^4.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", - "dev": true, - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/locate-path": { @@ -7363,76 +8115,89 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "dependencies": { - "type-fest": "^1.0.2" + "environment": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/logform": { @@ -7452,12 +8217,15 @@ } }, "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { @@ -7470,27 +8238,20 @@ } }, "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==", + "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, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "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/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -7498,39 +8259,34 @@ "dev": true }, "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "engines": { - "node": ">=12" + "node": ">= 0.6" } }, "node_modules/makeerror": { @@ -7559,9 +8315,12 @@ } }, "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==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -7585,6 +8344,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -7603,14 +8363,15 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=4.0.0" } }, "node_modules/mime-db": { @@ -7649,13 +8410,28 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -7683,40 +8459,38 @@ } }, "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" - }, + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "dependencies": { - "minipass": "^3.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "dependencies": { - "minipass": "^3.1.6", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -7734,6 +8508,24 @@ "node": ">= 8" } }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -7746,6 +8538,24 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", @@ -7758,32 +8568,41 @@ "node": ">=8" } }, - "node_modules/minipass/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/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { - "minipass": "^3.0.0", "yallist": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/minizlib/node_modules/yallist": { + "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -7868,18 +8687,39 @@ "url": "https://github.com/sponsors/raouldeheer" } }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "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 }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/needle": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", @@ -7914,6 +8754,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -7924,9 +8770,12 @@ } }, "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==" + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-config": { "version": "0.0.2", @@ -7937,142 +8786,68 @@ "node": ">=0.1.99" } }, - "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/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true }, "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-gyp/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==", - "dev": true - }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/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==", + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/node-gyp/node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "dependencies": { - "abbrev": "^1.0.0" + "isexe": "^3.1.1" }, "bin": { - "nopt": "bin/nopt.js" + "node-which": "bin/which.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-int64": { @@ -8082,24 +8857,24 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "dev": true, "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", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" @@ -8108,7 +8883,7 @@ "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" }, "funding": { "type": "opencollective", @@ -8125,13 +8900,40 @@ "concat-map": "0.0.1" } }, - "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/nodemon/node_modules/has-flag": { @@ -8155,13 +8957,16 @@ "node": "*" } }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "bin": { - "semver": "bin/semver" + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, "node_modules/nodemon/node_modules/supports-color": { @@ -8177,17 +8982,18 @@ } }, "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, "dependencies": { - "abbrev": "1" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -8200,69 +9006,61 @@ } }, "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" + "node_modules/npm-check-updates": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.2.tgz", + "integrity": "sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==", + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" }, "engines": { - "node": ">=4" + "node": ">=20.0.0", + "npm": ">=8.12.1" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "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" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/nssocket": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", - "integrity": "sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==", - "dev": true, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, "dependencies": { - "eventemitter2": "~0.4.14", - "lazy": "~1.0.11" + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" }, "engines": { - "node": ">= 0.10.x" + "node": "^14.16.0 || >=16.10.0" } }, - "node_modules/nssocket/node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", - "dev": true - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8272,9 +9070,9 @@ } }, "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "engines": { "node": ">= 6" } @@ -8290,6 +9088,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8340,6 +9144,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8357,34 +9167,13 @@ "node": ">= 0.8.0" } }, - "node_modules/os-filter-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", - "dev": true, - "dependencies": { - "arch": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=12.20" } }, "node_modules/p-limit": { @@ -8418,15 +9207,12 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8460,55 +9246,6 @@ "node": ">= 14" } }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/pac-resolver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", @@ -8522,6 +9259,12 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -8598,10 +9341,36 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -8612,18 +9381,23 @@ "node": ">=8" } }, - "node_modules/peek-readable": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", - "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true }, "node_modules/picocolors": { "version": "1.1.1", @@ -8667,15 +9441,6 @@ "node": ">=10" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -8685,6 +9450,15 @@ "node": ">= 6" } }, + "node_modules/piscina": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", + "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", + "dev": true, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -8749,6 +9523,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/plimit-lit": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", @@ -8762,37 +9547,37 @@ } }, "node_modules/pm2": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.4.3.tgz", - "integrity": "sha512-4/I1htIHzZk1Y67UgOCo4F1cJtas1kSds31N8zN0PybO230id1nigyjGuGFzUnGmUFPmrJ0On22fO1ChFlp7VQ==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.13.tgz", + "integrity": "sha512-1hS/adMgKoDpX4S1ichJW8SiGpex+oBSZK31dP1FSYOOGtaeuemXzhXPOCefmddgIY4K6v7uu+7xNPnmEnK3ag==", "dev": true, "dependencies": { - "@pm2/agent": "~2.0.0", - "@pm2/io": "~6.0.1", + "@pm2/agent": "~2.1.1", + "@pm2/blessed": "0.1.81", + "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "latest", - "async": "~3.2.0", - "blessed": "0.1.81", - "chalk": "3.0.0", - "chokidar": "^3.5.3", - "cli-tableau": "^2.0.0", + "ansis": "4.0.0-node10", + "async": "3.2.6", + "chokidar": "3.6.0", + "cli-tableau": "2.0.1", "commander": "2.15.1", - "croner": "~4.1.92", - "dayjs": "~1.11.5", - "debug": "^4.3.1", + "croner": "4.1.97", + "dayjs": "1.11.15", + "debug": "4.4.3", "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", - "js-yaml": "~4.1.0", + "js-yaml": "4.1.0", "mkdirp": "1.0.4", "needle": "2.4.0", - "pidusage": "~3.0", + "pidusage": "3.0.2", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", - "promptly": "^2", - "semver": "^7.2", + "promptly": "2.2.0", + "semver": "7.7.2", "source-map-support": "0.5.21", "sprintf-js": "1.1.2", "vizion": "~2.2.1" @@ -8804,7 +9589,7 @@ "pm2-runtime": "bin/pm2-runtime" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" }, "optionalDependencies": { "pm2-sysmonit": "^1.2.8" @@ -8886,17 +9671,28 @@ "node": ">=8" } }, - "node_modules/pm2/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/pm2/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=8" + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, "node_modules/pm2/node_modules/commander": { @@ -8905,6 +9701,42 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, + "node_modules/pm2/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pm2/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/pm2/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/pm2/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8934,15 +9766,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -8961,18 +9793,17 @@ } }, "node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -8988,37 +9819,39 @@ } }, "node_modules/prisma": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz", - "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==", - "dev": true, + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", + "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.16.2" + "@prisma/config": "6.18.0", + "@prisma/engines": "6.18.0" }, "bin": { - "prisma": "build/index.js", - "prisma2": "build/index.js" + "prisma": "build/index.js" }, "engines": { - "node": ">=14.17" + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "engines": { - "node": ">= 0.6.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -9041,19 +9874,6 @@ "read": "^1.0.4" } }, - "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, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -9067,15 +9887,15 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", - "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", @@ -9085,41 +9905,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -9129,48 +9914,18 @@ "node": ">=12" } }, - "node_modules/proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "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==", "dev": true }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, "node_modules/pstree.remy": { "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 }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "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", @@ -9180,12 +9935,28 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -9244,17 +10015,42 @@ } }, "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" } }, "node_modules/react-is": { @@ -9288,54 +10084,23 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "dev": true, - "dependencies": { - "readable-stream": "^4.7.0" - }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "engines": { - "node": ">=8" + "node": ">= 14.18.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "node_modules/require-directory": { "version": "2.1.1", @@ -9425,43 +10190,64 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "dependencies": { - "lowercase-keys": "^2.0.0" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "mimic-function": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -9487,19 +10273,19 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, - "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", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dependencies": { - "glob": "^7.1.3" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 18" } }, "node_modules/run-parallel": { @@ -9583,6 +10369,28 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -9622,68 +10430,51 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "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" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "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==", + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "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==" - }, - "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==", + "mime-db": "^1.54.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "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/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -9787,35 +10578,21 @@ "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==", + "dev": true }, "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "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, "dependencies": { - "semver": "~7.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9826,16 +10603,16 @@ } }, "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" @@ -9878,17 +10655,17 @@ } }, "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/sort-keys": { @@ -9950,15 +10727,15 @@ "dev": true }, "node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "dependencies": { - "minipass": "^3.1.1" + "minipass": "^7.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/stack-trace": { @@ -9991,13 +10768,24 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "engines": { "node": ">= 0.8" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -10012,20 +10800,41 @@ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "engines": { - "node": ">=0.6.19" + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/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==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/string-length/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==", "dev": true, "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/string-width": { @@ -10045,19 +10854,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/string-width-cjs": { + "name": "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==", "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "dev": true + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/string-width/node_modules/strip-ansi": { + "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", @@ -10072,10 +10920,12 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi": { + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10083,6 +10933,15 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -10092,13 +10951,14 @@ "node": ">=8" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" } }, "node_modules/strip-final-newline": { @@ -10122,29 +10982,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-2.0.0.tgz", - "integrity": "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strtok3": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", - "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "dev": true, "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.1.3" + "@tokenizer/token": "^0.3.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "type": "github", @@ -10152,51 +10999,36 @@ } }, "node_modules/superagent": { - "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 superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "dev": true, "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", + "form-data": "^4.0.4", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "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, - "bin": { - "mime": "cli.js" + "qs": "^6.11.2" }, "engines": { - "node": ">=4.0.0" + "node": ">=14.18.0" } }, "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==", - "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^8.1.2" + "superagent": "^10.2.3" }, "engines": { - "node": ">=6.4.0" + "node": ">=14.18.0" } }, "node_modules/supports-color": { @@ -10211,19 +11043,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -10331,11 +11150,11 @@ } }, "node_modules/swagger-ui-express": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", - "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", "dependencies": { - "swagger-ui-dist": ">=4.11.0" + "swagger-ui-dist": ">=5.0.0" }, "engines": { "node": ">= v0.10.32" @@ -10344,6 +11163,21 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/systeminformation": { "version": "5.27.11", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.11.tgz", @@ -10372,48 +11206,39 @@ } }, "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" + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/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/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "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==" - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, "node_modules/test-exclude": { @@ -10440,6 +11265,27 @@ "concat-map": "0.0.1" } }, + "node_modules/test-exclude/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", + "dev": true, + "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/test-exclude/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10452,17 +11298,77 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "devOptional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -10490,11 +11396,12 @@ } }, "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", "dev": true, "dependencies": { + "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -10515,35 +11422,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/trim-repeated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", - "integrity": "sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/trim-repeated/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -10552,38 +11430,56 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^28.0.0", - "babel-jest": "^28.0.0", - "jest": "^28.0.0", - "typescript": ">=4.3" + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, @@ -10592,9 +11488,24 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -10656,7 +11567,31 @@ "tsc-alias": "dist/bin/index.js" }, "engines": { - "node": ">=16.20.2" + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, "node_modules/tsc-alias/node_modules/commander": { @@ -10668,6 +11603,30 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/tsc-alias/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tsc-alias/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -10692,30 +11651,9 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tv4": { "version": "1.3.0", @@ -10758,9 +11696,9 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "engines": { "node": ">=10" @@ -10787,16 +11725,51 @@ "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" } }, "node_modules/undefsafe": { @@ -10805,28 +11778,34 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true + }, "node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "dependencies": { - "unique-slug": "^3.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unpipe": { @@ -10837,6 +11816,40 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -10881,14 +11894,6 @@ "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==", - "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", @@ -10909,12 +11914,6 @@ "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "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 - }, "node_modules/validator": { "version": "13.15.15", "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", @@ -10964,20 +11963,6 @@ "makeerror": "1.0.12" } }, - "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/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10993,40 +11978,6 @@ "node": ">= 8" } }, - "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/wide-align/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/wide-align/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/wide-align/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/winston": { "version": "3.18.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", @@ -11049,14 +12000,14 @@ } }, "node_modules/winston-daily-rotate-file": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", - "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", "dependencies": { "file-stream-rotator": "^0.6.1", - "object-hash": "^2.0.1", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" }, "engines": { "node": ">=8" @@ -11078,17 +12029,6 @@ "node": ">= 12.0.0" } }, - "node_modules/winston/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -11098,6 +12038,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -11115,43 +12061,84 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/wrap-ansi-cjs/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==", "dev": true, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/wrap-ansi-cjs/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==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^5.0.1" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/wrappy": { @@ -11160,16 +12147,28 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "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==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ws": { @@ -11209,12 +12208,15 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { @@ -11244,6 +12246,15 @@ "node": ">=12" } }, + "node_modules/yargs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11273,6 +12284,31 @@ "node": ">=8" } }, + "node_modules/yargs/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index df3861f..b3c5873 100644 --- a/package.json +++ b/package.json @@ -22,63 +22,65 @@ "schema": "src/prisma/schema.prisma" }, "dependencies": { - "@prisma/client": "^4.1.0", - "bcrypt": "^5.0.1", + "@prisma/client": "^6.18.0", + "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", - "class-validator": "^0.13.2", - "compression": "^1.7.4", - "cookie-parser": "^1.4.6", + "class-validator": "^0.14.2", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", - "dotenv": "^16.0.1", - "envalid": "^7.3.1", - "express": "^4.18.1", - "helmet": "^5.1.1", + "dotenv": "^17.2.3", + "envalid": "^8.1.0", + "express": "^5.1.0", + "helmet": "^8.1.0", "hpp": "^0.2.3", - "jsonwebtoken": "^8.5.1", - "morgan": "^1.10.0", - "reflect-metadata": "^0.1.13", - "swagger-jsdoc": "^6.2.1", - "swagger-ui-express": "^4.5.0", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.1", + "npm-check-updates": "^19.1.2", + "reflect-metadata": "^0.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", - "winston": "^3.8.1", - "winston-daily-rotate-file": "^4.7.1" + "winston": "^3.18.3", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { - "@swc/cli": "^0.1.57", - "@swc/core": "^1.2.220", - "@types/bcrypt": "^5.0.0", - "@types/compression": "^1.7.2", - "@types/cookie-parser": "^1.4.3", - "@types/cors": "^2.8.12", - "@types/express": "^4.17.13", - "@types/hpp": "^0.2.2", - "@types/jest": "^28.1.6", - "@types/jsonwebtoken": "^8.5.8", - "@types/morgan": "^1.9.3", - "@types/node": "^17.0.45", - "@types/supertest": "^2.0.12", - "@types/swagger-jsdoc": "^6.0.1", - "@types/swagger-ui-express": "^4.1.3", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", - "cross-env": "^7.0.3", - "eslint": "^8.20.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.2.1", - "husky": "^8.0.1", - "jest": "^28.1.1", - "lint-staged": "^13.0.3", + "@swc/cli": "^0.7.8", + "@swc/core": "^1.14.0", + "@types/bcrypt": "^6.0.0", + "@types/compression": "^1.8.1", + "@types/cookie-parser": "^1.4.10", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/hpp": "^0.2.7", + "@types/jest": "^30.0.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/morgan": "^1.9.10", + "@types/node": "^24.9.2", + "@types/supertest": "^6.0.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "@typescript-eslint/eslint-plugin": "^8.46.2", + "@typescript-eslint/parser": "^8.46.2", + "cross-env": "^10.1.0", + "dotenv-cli": "^11.0.0", + "eslint": "^9.38.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "husky": "^9.1.7", + "jest": "^30.2.0", + "lint-staged": "^16.2.6", "node-config": "^0.0.2", - "node-gyp": "^9.1.0", - "nodemon": "^2.0.19", - "pm2": "^5.2.0", - "prettier": "^2.7.1", - "prisma": "^4.1.0", - "supertest": "^6.2.4", - "ts-jest": "^28.0.7", - "ts-node": "^10.9.1", - "tsc-alias": "^1.7.0", - "tsconfig-paths": "^4.0.0", - "typescript": "^4.7.4" + "node-gyp": "^11.5.0", + "nodemon": "^3.1.10", + "pm2": "^6.0.13", + "prettier": "^3.6.2", + "prisma": "^6.18.0", + "supertest": "^7.1.4", + "ts-jest": "^29.4.5", + "ts-node": "^10.9.2", + "tsc-alias": "^1.8.16", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.3" } -} \ No newline at end of file +} diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index e1f6077..2674665 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -1,4 +1,6 @@ -import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength } from 'class-validator'; +import { Gender } from '@prisma/client'; +import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDate } from 'class-validator'; +import { Type } from 'class-transformer'; export class CreateUserDto { @IsEmail() @@ -12,6 +14,15 @@ export class CreateUserDto { @IsNotEmpty() public phone: string; + @IsString() + @IsNotEmpty() + public gender: Gender; + + @IsNotEmpty() + @Type(() => Date) + @IsDate() + public date_of_birth: Date; + @IsString() @IsNotEmpty() @MinLength(8) diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index 48ef819..14e3875 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -5,6 +5,7 @@ export interface Clinic { is_active: boolean; opening_at: Date; closing_at: Date; + address: string; created_at: Date; modified_at: Date; deleted_at?: Date; diff --git a/src/interfaces/medications.interface.ts b/src/interfaces/medications.interface.ts index f5bd0ad..f050fbc 100644 --- a/src/interfaces/medications.interface.ts +++ b/src/interfaces/medications.interface.ts @@ -11,6 +11,7 @@ export interface Medication { frequency: number; period: Period; description?: string; + category: string; created_at: Date; modified_at: Date; deleted_at?: Date; diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 223f268..d7a1a5e 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -3,6 +3,7 @@ import { Medication } from './medications.interface'; import { ScanLab } from './scans-labs.interface'; import { ClinicNurse, ClinicDoctor } from './clinics.interface'; import { AuditLog } from './audit-logs.interface'; +import { Gender } from '@prisma/client'; export interface User { id: string; @@ -10,6 +11,8 @@ export interface User { email: string; username: string; phone: string; + gender: Gender; + date_of_birth: Date; password_hash: string; created_at: Date; modified_at: Date; @@ -47,4 +50,3 @@ export interface Doctor { clinic_doctors?: ClinicDoctor[]; } - diff --git a/src/prisma/migrations/20251029183425_init/migration.sql b/src/prisma/migrations/20251031175320_init_schema/migration.sql similarity index 77% rename from src/prisma/migrations/20251029183425_init/migration.sql rename to src/prisma/migrations/20251031175320_init_schema/migration.sql index bdfa420..4ac770a 100644 --- a/src/prisma/migrations/20251029183425_init/migration.sql +++ b/src/prisma/migrations/20251031175320_init_schema/migration.sql @@ -7,6 +7,9 @@ CREATE TYPE "Action" AS ENUM ('CREATE', 'UPDATE', 'DELETE', 'READ', 'LOGIN', 'LO -- CreateEnum CREATE TYPE "Period" AS ENUM ('DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'); +-- CreateEnum +CREATE TYPE "Gender" AS ENUM ('MALE', 'FEMALE'); + -- CreateTable CREATE TABLE "Users" ( "id" TEXT NOT NULL, @@ -15,6 +18,8 @@ CREATE TABLE "Users" ( "username" VARCHAR(255) NOT NULL, "phone" VARCHAR(20) NOT NULL, "password_hash" VARCHAR(255) NOT NULL, + "gender" "Gender" NOT NULL, + "date_of_birth" TIMESTAMP(3) NOT NULL, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "modified_at" TIMESTAMP(3) NOT NULL, "deleted_at" TIMESTAMP(3), @@ -44,8 +49,8 @@ CREATE TABLE "Patient" ( -- CreateTable CREATE TABLE "Appointments" ( "id" TEXT NOT NULL, - "patient_id" TEXT NOT NULL, - "doctor_id" TEXT NOT NULL, + "patient_id" TEXT, + "doctor_id" TEXT, "scheduled_time" TIMESTAMP(3) NOT NULL, "is_online" BOOLEAN NOT NULL DEFAULT false, "is_completed" BOOLEAN NOT NULL DEFAULT false, @@ -63,6 +68,7 @@ CREATE TABLE "Medications" ( "patient_id" TEXT NOT NULL, "doctor_id" TEXT NOT NULL, "treatment_name" VARCHAR(255) NOT NULL, + "category" VARCHAR(100) NOT NULL, "medication_end_date" TIMESTAMP(3) NOT NULL, "medication_start_time" TIME(0) NOT NULL, "frequency" INTEGER NOT NULL, @@ -100,6 +106,7 @@ CREATE TABLE "Clinic" ( "is_active" BOOLEAN NOT NULL DEFAULT true, "opening_at" TIME(0) NOT NULL, "closing_at" TIME(0) NOT NULL, + "address" VARCHAR(300) NOT NULL, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "modified_at" TIMESTAMP(3) NOT NULL, "deleted_at" TIMESTAMP(3), @@ -142,11 +149,50 @@ CREATE UNIQUE INDEX "Users_email_key" ON "Users"("email"); -- CreateIndex CREATE UNIQUE INDEX "Users_username_key" ON "Users"("username"); +-- CreateIndex +CREATE INDEX "Appointments_patient_id_idx" ON "Appointments"("patient_id"); + +-- CreateIndex +CREATE INDEX "Appointments_doctor_id_idx" ON "Appointments"("doctor_id"); + +-- CreateIndex +CREATE INDEX "Appointments_scheduled_time_idx" ON "Appointments"("scheduled_time"); + +-- CreateIndex +CREATE INDEX "Medications_patient_id_idx" ON "Medications"("patient_id"); + +-- CreateIndex +CREATE INDEX "Medications_doctor_id_idx" ON "Medications"("doctor_id"); + +-- CreateIndex +CREATE INDEX "Scans_Labs_patient_id_idx" ON "Scans_Labs"("patient_id"); + +-- CreateIndex +CREATE INDEX "Scans_Labs_doctor_id_idx" ON "Scans_Labs"("doctor_id"); + +-- CreateIndex +CREATE INDEX "ClinicNurse_nurse_id_idx" ON "ClinicNurse"("nurse_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "ClinicNurse_clinic_id_nurse_id_key" ON "ClinicNurse"("clinic_id", "nurse_id"); + +-- CreateIndex +CREATE INDEX "ClinicDoctor_doctor_id_idx" ON "ClinicDoctor"("doctor_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "ClinicDoctor_clinic_id_doctor_id_key" ON "ClinicDoctor"("clinic_id", "doctor_id"); + +-- CreateIndex +CREATE INDEX "AuditLogs_user_id_idx" ON "AuditLogs"("user_id"); + +-- CreateIndex +CREATE INDEX "AuditLogs_created_at_idx" ON "AuditLogs"("created_at"); + -- AddForeignKey -ALTER TABLE "Doctor" ADD CONSTRAINT "Doctor_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "Doctor" ADD CONSTRAINT "Doctor_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "Patient" ADD CONSTRAINT "Patient_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "Patient" ADD CONSTRAINT "Patient_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Patient" ADD CONSTRAINT "Patient_controlling_nurse_id_fkey" FOREIGN KEY ("controlling_nurse_id") REFERENCES "Users"("id") ON DELETE SET NULL ON UPDATE CASCADE; @@ -170,16 +216,16 @@ ALTER TABLE "Scans_Labs" ADD CONSTRAINT "Scans_Labs_patient_id_fkey" FOREIGN KEY ALTER TABLE "Scans_Labs" ADD CONSTRAINT "Scans_Labs_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_nurse_id_fkey" FOREIGN KEY ("nurse_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "ClinicNurse" ADD CONSTRAINT "ClinicNurse_nurse_id_fkey" FOREIGN KEY ("nurse_id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "ClinicDoctor" ADD CONSTRAINT "ClinicDoctor_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "AuditLogs" ADD CONSTRAINT "AuditLogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/migrations/migration_lock.toml b/src/prisma/migrations/migration_lock.toml index fbffa92..044d57c 100644 --- a/src/prisma/migrations/migration_lock.toml +++ b/src/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index ef9193b..f8fcd32 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -24,16 +24,16 @@ model User { deleted_at DateTime? // Relations - patient Patient? - doctor Doctor? + patient Patient? @relation("UserAsPatient") + doctor Doctor? @relation("UserAsDoctor") appointments_as_patient Appointment[] @relation("PatientAppointments") appointments_as_doctor Appointment[] @relation("DoctorAppointments") medications_as_patient Medication[] @relation("PatientMedications") medications_as_doctor Medication[] @relation("DoctorMedications") scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") - clinics_as_nurse ClinicNurse[] - audit_logs AuditLog[] + clinics_as_nurse ClinicNurse[] @relation("NurseClinics") + audit_logs AuditLog[] @relation("UserAuditLogs") controlled_patients Patient[] @relation("ControllingNurse") @@map("Users") @@ -45,7 +45,7 @@ model Doctor { avg_time DateTime? @db.Time(0) // Relations - user User @relation(fields: [id], references: [id]) + user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) clinic_doctors ClinicDoctor[] @@map("Doctor") @@ -58,16 +58,16 @@ model Patient { controlling_nurse_id String? // Relations - user User @relation(fields: [id], references: [id]) - controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id]) + user User @relation("UserAsPatient", fields: [id], references: [id], onDelete: Cascade) + controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id], onDelete: SetNull) @@map("Patient") } model Appointment { id String @id @default(uuid()) - patient_id String - doctor_id String + patient_id String? + doctor_id String? scheduled_time DateTime is_online Boolean @default(false) is_completed Boolean @default(false) @@ -77,9 +77,12 @@ model Appointment { deleted_at DateTime? // Relations - patient User @relation("PatientAppointments", fields: [patient_id], references: [id]) - doctor User @relation("DoctorAppointments", fields: [doctor_id], references: [id]) + patient User? @relation("PatientAppointments", fields: [patient_id], references: [id], onDelete: Restrict) + doctor User? @relation("DoctorAppointments", fields: [doctor_id], references: [id], onDelete: Restrict) + @@index([patient_id]) + @@index([doctor_id]) + @@index([scheduled_time]) @@map("Appointments") } @@ -99,9 +102,11 @@ model Medication { deleted_at DateTime? // Relations - patient User @relation("PatientMedications", fields: [patient_id], references: [id]) - doctor User @relation("DoctorMedications", fields: [doctor_id], references: [id]) + patient User @relation("PatientMedications", fields: [patient_id], references: [id], onDelete: Restrict) + doctor User @relation("DoctorMedications", fields: [doctor_id], references: [id], onDelete: Restrict) + @@index([patient_id]) + @@index([doctor_id]) @@map("Medications") } @@ -121,9 +126,11 @@ model ScanLab { deleted_at DateTime? // Relations - patient User @relation("PatientScansLabs", fields: [patient_id], references: [id]) - doctor User @relation("DoctorScansLabs", fields: [doctor_id], references: [id]) + patient User @relation("PatientScansLabs", fields: [patient_id], references: [id], onDelete: Restrict) + doctor User @relation("DoctorScansLabs", fields: [doctor_id], references: [id], onDelete: Restrict) + @@index([patient_id]) + @@index([doctor_id]) @@map("Scans_Labs") } @@ -150,9 +157,11 @@ model ClinicNurse { nurse_id String // Relations - clinic Clinic @relation(fields: [clinic_id], references: [id]) - nurse User @relation(fields: [nurse_id], references: [id]) + clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + nurse User @relation("NurseClinics", fields: [nurse_id], references: [id], onDelete: Cascade) + @@unique([clinic_id, nurse_id]) + @@index([nurse_id]) @@map("ClinicNurse") } @@ -162,9 +171,11 @@ model ClinicDoctor { doctor_id String // Relations - clinic Clinic @relation(fields: [clinic_id], references: [id]) - doctor Doctor @relation(fields: [doctor_id], references: [id]) + clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + @@unique([clinic_id, doctor_id]) + @@index([doctor_id]) @@map("ClinicDoctor") } @@ -176,8 +187,10 @@ model AuditLog { created_at DateTime @default(now()) // Relations - user User @relation(fields: [user_id], references: [id]) + user User @relation("UserAuditLogs", fields: [user_id], references: [id], onDelete: Restrict) + @@index([user_id]) + @@index([created_at]) @@map("AuditLogs") } diff --git a/src/server.ts b/src/server.ts index 2362805..d2a4e01 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,6 +5,6 @@ import { ValidateEnv } from '@utils/validateEnv'; ValidateEnv(); -const app = new App([new UserRoute(), new AuthRoute()]); +const app = new App([new AuthRoute()]); app.listen(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index d042d23..c69e77c 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -22,8 +22,8 @@ export class AuthService { const hashedPassword = await hash(userData.password, 10); const username = emailHandle; - - const createUserData: Promise = this.users.create({ data: { ...userData, username ,password_hash: hashedPassword } }); + const { password, ...userDataWithoutPassword } = userData; + const createUserData: Promise = this.users.create({ data: { ...userDataWithoutPassword, username ,password_hash: hashedPassword } }); return createUserData; } From 5982e1ef2da757963ff5a828779c2316fd60c480 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 1 Nov 2025 00:18:58 +0200 Subject: [PATCH 009/210] added access token and refresh token implementation --- src/config/index.ts | 4 +- src/controllers/auth.controller.ts | 30 +++- src/dtos/users.dto.ts | 6 +- src/interfaces/auth.interface.ts | 12 +- src/middlewares/auth.middleware.ts | 3 +- .../migration.sql | 24 +++ src/prisma/schema.prisma | 41 ++++-- src/routes/auth.route.ts | 1 + src/services/auth.service.ts | 139 ++++++++++++++++-- 9 files changed, 230 insertions(+), 30 deletions(-) create mode 100644 src/prisma/migrations/20251031214538_refresh_token_table/migration.sql diff --git a/src/config/index.ts b/src/config/index.ts index ef17df5..548ccf4 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,4 +2,6 @@ import { config } from 'dotenv'; config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); export const CREDENTIALS = process.env.CREDENTIALS === 'true'; -export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN } = process.env; +export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET } = process.env; +export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days +export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index cc5a9e1..1e18d1a 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -22,9 +22,10 @@ export class AuthController { public logIn = async (req: Request, res: Response, next: NextFunction): Promise => { try { const userData: LoginUserDto = req.body; - const { cookie, findUser } = await this.auth.login(userData); - - res.setHeader('Set-Cookie', [cookie]); + const { cookies, findUser } = await this.auth.login(userData); + console.log(cookies); + + res.setHeader('Set-Cookie', cookies); res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); } catch (error) { next(error); @@ -36,10 +37,31 @@ export class AuthController { const userData: User = req.user; const logOutUserData: User = await this.auth.logout(userData); - res.setHeader('Set-Cookie', ['Authorization=; Max-age=0']); + res.setHeader('Set-Cookie', ['Authorization=; Max-age=0', 'RefreshToken=; Max-age=0']); res.status(200).json({ message: 'Logged Out Successfully' }); } catch (error) { next(error); } }; + + public refresh = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const refreshToken = req.cookies?.RefreshToken; + const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); + + res.setHeader('Set-Cookie', cookies); + res.status(200).json({ + data: { + user, + accessToken: { + expiresIn: accessToken.expiresIn, + expiresAt: new Date(Date.now() + accessToken.expiresIn * 1000) + } + }, + message: 'Token Refreshed Successfully' + }); + } catch (error) { + next(error); + } + }; } diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 2674665..aa0a561 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -1,5 +1,5 @@ import { Gender } from '@prisma/client'; -import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDate } from 'class-validator'; +import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDate, IsBoolean, IsOptional } from 'class-validator'; import { Type } from 'class-transformer'; export class CreateUserDto { @@ -37,6 +37,10 @@ export class LoginUserDto { @IsString() @IsNotEmpty() public password: string; + + @IsOptional() + @IsBoolean() + public rememberMe?: boolean; } export class UpdateUserDto { diff --git a/src/interfaces/auth.interface.ts b/src/interfaces/auth.interface.ts index 0e9335b..aa930a4 100644 --- a/src/interfaces/auth.interface.ts +++ b/src/interfaces/auth.interface.ts @@ -5,11 +5,21 @@ export interface DataStoredInToken { id: string; } -export interface TokenData { +export interface AccessTokenData { token: string; expiresIn: number; } +export interface RefreshTokenData { + token: string; + expiresIn: number; +} + +export interface TokenResponse { + accessToken: AccessTokenData; + refreshToken?: RefreshTokenData; +} + export interface RequestWithUser extends Request { user: User; } diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 1523915..3aa6ab1 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -18,8 +18,7 @@ const getAuthorization = (req: Request) => { export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: NextFunction) => { try { - const Authorization = getAuthorization(req); - + const Authorization = getAuthorization(req); if (Authorization) { const { id } = (await verify(Authorization, SECRET_KEY)) as DataStoredInToken; const users = new PrismaClient().user; diff --git a/src/prisma/migrations/20251031214538_refresh_token_table/migration.sql b/src/prisma/migrations/20251031214538_refresh_token_table/migration.sql new file mode 100644 index 0000000..59ff378 --- /dev/null +++ b/src/prisma/migrations/20251031214538_refresh_token_table/migration.sql @@ -0,0 +1,24 @@ +-- CreateTable +CREATE TABLE "RefreshTokens" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "token_hash" VARCHAR(255) NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "is_revoked" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "revoked_at" TIMESTAMP(3), + + CONSTRAINT "RefreshTokens_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "RefreshTokens_user_id_idx" ON "RefreshTokens"("user_id"); + +-- CreateIndex +CREATE INDEX "RefreshTokens_token_hash_idx" ON "RefreshTokens"("token_hash"); + +-- CreateIndex +CREATE INDEX "RefreshTokens_expires_at_idx" ON "RefreshTokens"("expires_at"); + +-- AddForeignKey +ALTER TABLE "RefreshTokens" ADD CONSTRAINT "RefreshTokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index f8fcd32..a7408ad 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -24,17 +24,18 @@ model User { deleted_at DateTime? // Relations - patient Patient? @relation("UserAsPatient") - doctor Doctor? @relation("UserAsDoctor") - appointments_as_patient Appointment[] @relation("PatientAppointments") - appointments_as_doctor Appointment[] @relation("DoctorAppointments") - medications_as_patient Medication[] @relation("PatientMedications") - medications_as_doctor Medication[] @relation("DoctorMedications") - scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") - scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") - clinics_as_nurse ClinicNurse[] @relation("NurseClinics") - audit_logs AuditLog[] @relation("UserAuditLogs") - controlled_patients Patient[] @relation("ControllingNurse") + patient Patient? @relation("UserAsPatient") + doctor Doctor? @relation("UserAsDoctor") + appointments_as_patient Appointment[] @relation("PatientAppointments") + appointments_as_doctor Appointment[] @relation("DoctorAppointments") + medications_as_patient Medication[] @relation("PatientMedications") + medications_as_doctor Medication[] @relation("DoctorMedications") + scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") + clinics_as_nurse ClinicNurse[] @relation("NurseClinics") + audit_logs AuditLog[] @relation("UserAuditLogs") + controlled_patients Patient[] @relation("ControllingNurse") + refresh_tokens RefreshToken[] @relation("UserRefreshTokens") @@map("Users") } @@ -194,6 +195,24 @@ model AuditLog { @@map("AuditLogs") } +model RefreshToken { + id String @id @default(uuid()) + user_id String + token_hash String @db.VarChar(255) + expires_at DateTime + is_revoked Boolean @default(false) + created_at DateTime @default(now()) + revoked_at DateTime? + + // Relations + user User @relation("UserRefreshTokens", fields: [user_id], references: [id], onDelete: Cascade) + + @@index([user_id]) + @@index([token_hash]) + @@index([expires_at]) + @@map("RefreshTokens") +} + enum ScanLabType { SCAN diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ecfe266..e6604b8 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -18,5 +18,6 @@ export class AuthRoute implements Routes { this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); + this.router.post(`${this.path}/refresh`, this.auth.refresh); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index c69e77c..b44488d 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,16 +1,18 @@ import { PrismaClient } from '@prisma/client'; import { compare, hash } from 'bcrypt'; -import { sign } from 'jsonwebtoken'; +import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; -import { SECRET_KEY } from '@config'; +import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EXPIRY } from '@config'; import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; -import { DataStoredInToken, TokenData } from '@interfaces/auth.interface'; +import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; +import crypto from 'crypto'; @Service() export class AuthService { public users = new PrismaClient().user; + public refreshTokens = new PrismaClient().refreshToken; public async signup(userData: CreateUserDto): Promise { const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); @@ -28,35 +30,152 @@ export class AuthService { return createUserData; } - public async login(userData: LoginUserDto): Promise<{ cookie: string; findUser: User }> { + public async login(userData: LoginUserDto): Promise<{ cookies: string[]; findUser: User }> { const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); if (!findUser) throw new HttpException(409, `This email ${userData.email} was not found`); const isPasswordMatching: boolean = await compare(userData.password, findUser.password_hash); if (!isPasswordMatching) throw new HttpException(409, 'Password is not matching'); - const tokenData = this.createToken(findUser); - const cookie = this.createCookie(tokenData); + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); + const cookies = this.createCookies(tokenResponse); - return { cookie, findUser }; + return { cookies, findUser }; } public async logout(userData: User): Promise { const findUser: User = await this.users.findFirst({ where: { email: userData.email, password_hash: userData.password_hash } }); if (!findUser) throw new HttpException(409, "User doesn't exist"); + // Revoke all refresh tokens for this user + await this.refreshTokens.updateMany({ + where: { user_id: findUser.id, is_revoked: false }, + data: { is_revoked: true, revoked_at: new Date() }, + }); + return findUser; } - public createToken(user: User): TokenData { + public async createTokens(user: User, rememberMe: boolean = false): Promise { + const accessToken = this.createAccessToken(user); + + if (rememberMe) { + const refreshToken = await this.createRefreshToken(user); + return { accessToken, refreshToken }; + } + + return { accessToken }; + } + + public createAccessToken(user: User): AccessTokenData { const dataStoredInToken: DataStoredInToken = { id: user.id }; const secretKey: string = SECRET_KEY; - const expiresIn: number = 60 * 60; + const expiresIn: number = this.parseTimeToSeconds(ACCESS_TOKEN_EXPIRY); return { expiresIn, token: sign(dataStoredInToken, secretKey, { expiresIn }) }; } - public createCookie(tokenData: TokenData): string { + public async createRefreshToken(user: User): Promise { + const dataStoredInToken: DataStoredInToken = { id: user.id }; + const secretKey: string = REFRESH_TOKEN_SECRET; + const expiresIn: number = this.parseTimeToSeconds(REFRESH_TOKEN_EXPIRY); + + const token = sign(dataStoredInToken, secretKey, { expiresIn }); + + // Hash the token before storing + const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); + + // Store refresh token in database + await this.refreshTokens.create({ + data: { + user_id: user.id, + token_hash: tokenHash, + expires_at: new Date(Date.now() + expiresIn * 1000), + }, + }); + + return { expiresIn, token }; + } + + public createCookies(tokenResponse: TokenResponse): string[] { + const cookies: string[] = []; + + // Access token cookie + cookies.push(`Authorization=${tokenResponse.accessToken.token}; HttpOnly; Max-Age=${tokenResponse.accessToken.expiresIn}; Path=/; SameSite=Strict`); + + // Refresh token cookie (if exists) + if (tokenResponse.refreshToken) { + cookies.push(`RefreshToken=${tokenResponse.refreshToken.token}; HttpOnly; Max-Age=${tokenResponse.refreshToken.expiresIn}; Path=/; SameSite=Strict`); + } + + return cookies; + } + + public async refreshAccessToken(refreshToken: string): Promise<{ cookies: string[]; user: User; accessToken: AccessTokenData }> { + if (!refreshToken) throw new HttpException(401, 'Refresh token not provided'); + + try { + // Verify the refresh token + const secretKey: string = REFRESH_TOKEN_SECRET; + const decoded = verify(refreshToken, secretKey) as DataStoredInToken; + + // Hash the token to compare with stored hash + const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); + + // Check if refresh token exists and is not revoked + const storedToken = await this.refreshTokens.findFirst({ + where: { + token_hash: tokenHash, + user_id: decoded.id, + is_revoked: false, + expires_at: { gt: new Date() }, + }, + }); + + if (!storedToken) throw new HttpException(401, 'Invalid or expired refresh token'); + + // Get user + const user = await this.users.findUnique({ where: { id: decoded.id } }); + if (!user) throw new HttpException(401, 'User not found'); + + // Create new access token + const accessToken = this.createAccessToken(user); + const cookies = this.createCookies({ accessToken }); + + return { cookies, user, accessToken }; + } catch (error) { + throw new HttpException(401, 'Invalid refresh token'); + } + } + + // public async revokeRefreshToken(refreshToken: string): Promise { + // const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); + + // await this.refreshTokens.updateMany({ + // where: { token_hash: tokenHash, is_revoked: false }, + // data: { is_revoked: true, revoked_at: new Date() }, + // }); + // } + + private parseTimeToSeconds(timeString: string): number { + const unit = timeString.slice(-1); + const value = parseInt(timeString.slice(0, -1)); + + switch (unit) { + case 's': return value; + case 'm': return value * 60; + case 'h': return value * 60 * 60; + case 'd': return value * 24 * 60 * 60; + default: return 3600; // Default 1 hour + } + } + + // Keep old methods for backward compatibility + public createToken(user: User): AccessTokenData { + return this.createAccessToken(user); + } + + public createCookie(tokenData: AccessTokenData): string { return `Authorization=${tokenData.token}; HttpOnly; Max-Age=${tokenData.expiresIn};`; } } From 116bc71bb99c7208e6072212cd242b47fd4936d5 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 1 Nov 2025 12:46:07 +0200 Subject: [PATCH 010/210] upgrade dependencies / docker modification --- Dockerfile.dev | 41 - Dockerfile.prod | 19 - Makefile | 46 - docker-compose.yml | 12 +- package-lock.json | 2095 +++++++++++++++++++++++++++++++------------- package.json | 4 +- 6 files changed, 1506 insertions(+), 711 deletions(-) delete mode 100644 Dockerfile.dev delete mode 100644 Dockerfile.prod delete mode 100644 Makefile diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 07ac86d..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,41 +0,0 @@ -# First stage: BUILD THE APP # - -# NodeJS Version 22 -FROM node:22-bullseye AS builder - -# Work to Dir -WORKDIR /app - -#copy package and prisma files -COPY package*.json ./ -COPY src/prisma ./src/prisma - -# Install Node Package -RUN npm ci --legacy-peer-deps - -# generate prisma client -RUN npx prisma generate - -# Copy rest of the app -COPY . . - - - -# second stage # - -FROM node:22-bullseye AS runner - -WORKDIR /app - -# copy only needed files -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/src/prisma ./src/prisma -COPY --from=builder /app/package*.json ./ - -# Set Env -ENV NODE_ENV=development - -EXPOSE 3000 - -# Cmd script -CMD ["npm", "run", "dev"] diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index ba23202..0000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,19 +0,0 @@ -# NodeJS Version 16 -FROM node:16.18-buster-slim - -# Copy Dir -COPY . ./app - -# Work to Dir -WORKDIR /app - -# Install Node Package -RUN npm install --legacy-peer-deps - -# Set Env -ENV NODE_ENV production - -EXPOSE 3000 - -# Cmd script -CMD ["npm", "run", "start"] diff --git a/Makefile b/Makefile deleted file mode 100644 index b8caca3..0000000 --- a/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -# app name should be overridden. -# ex) production-stage: make build APP_NAME= -# ex) development-stage: make build-dev APP_NAME= - -SHELL := /bin/bash - -APP_NAME = typescript-express -APP_NAME := $(APP_NAME) - -.PHONY: help start clean db test - -help: - @grep -E '^[1-9a-zA-Z_-]+:.*?## .*$$|(^#--)' $(MAKEFILE_LIST) \ - | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m %-43s\033[0m %s\n", $$1, $$2}' \ - | sed -e 's/\[32m #-- /[33m/' - -#-- Docker -up: ## Up the container images - docker-compose up -d - -down: ## Down the container images - docker-compose down - -build: ## Build the container image - Production - docker build -t ${APP_NAME}\ - -f Dockerfile.prod . - -build-dev: ## Build the container image - Development - docker build -t ${APP_NAME}\ - -f Dockerfile.dev . - -run: ## Run the container image - docker run -d -it -p 3000:3000 ${APP_NAME} - -pause: ## Pause the containers - docker container rm -f ${APP_NAME} - -clean: ## Clean the images - docker rmi -f ${APP_NAME} - -remove: ## Remove the volumes - docker volume rm -f ${APP_NAME} - -#-- Database -db: ## Start the local database MySQL - docker-compose up -d mysql diff --git a/docker-compose.yml b/docker-compose.yml index a60e6ef..43a54e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,12 +17,12 @@ services: container_name: server build: context: ./ - dockerfile: Dockerfile.dev + dockerfile: Dockerfile ports: - - "3000:3000" + - "3000:3000" - "5555:5555" environment: - DATABASE_URL: "postgresql://myuser:mypassword@postgres:5432/mydatabase?schema=public" + DATABASE_URL: "postgresql://NG:password@postgres:5432/EMR_DB?schema=public" NODE_ENV: development volumes: - ./:/app @@ -39,9 +39,9 @@ services: container_name: postgres_db image: postgres:16 environment: - - POSTGRES_USER=myuser - - POSTGRES_PASSWORD=mypassword - - POSTGRES_DB=mydatabase + - POSTGRES_USER=NG + - POSTGRES_PASSWORD=password + - POSTGRES_DB=EMR_DB volumes: - data:/var/lib/postgresql/data restart: unless-stopped diff --git a/package-lock.json b/package-lock.json index 48d31db..3faa179 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", - "npm-check-updates": "^19.1.2", "reflect-metadata": "^0.2.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", @@ -50,8 +49,7 @@ "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", "cross-env": "^10.1.0", - "dotenv-cli": "^11.0.0", - "eslint": "^9.38.0", + "eslint": "^9.39.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", "husky": "^9.1.7", @@ -75,6 +73,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.6", @@ -86,6 +85,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", "engines": { "node": ">=10" } @@ -93,12 +93,14 @@ "node_modules/@apidevtools/swagger-methods": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", "@apidevtools/openapi-schemas": "^2.0.4", @@ -116,6 +118,7 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -130,6 +133,7 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -139,6 +143,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -169,6 +174,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" } @@ -178,6 +184,7 @@ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", @@ -194,6 +201,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -210,6 +218,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" } @@ -219,6 +228,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -228,6 +238,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -241,6 +252,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -258,6 +270,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -267,6 +280,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -276,6 +290,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -285,6 +300,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -294,6 +310,7 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" @@ -307,6 +324,7 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" }, @@ -322,6 +340,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" }, @@ -334,6 +353,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" }, @@ -346,6 +366,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" }, @@ -358,6 +379,7 @@ "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" }, @@ -373,6 +395,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -388,6 +411,7 @@ "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" }, @@ -400,6 +424,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" }, @@ -412,6 +437,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -427,6 +453,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" }, @@ -439,6 +466,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" }, @@ -451,6 +479,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" }, @@ -463,6 +492,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" }, @@ -475,6 +505,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" }, @@ -487,6 +518,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" }, @@ -499,6 +531,7 @@ "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" }, @@ -514,6 +547,7 @@ "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" }, @@ -529,6 +563,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -544,6 +579,7 @@ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -558,6 +594,7 @@ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -576,6 +613,7 @@ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -588,13 +626,15 @@ "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/@borewit/text-codec": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", "dev": true, + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/Borewit" @@ -604,6 +644,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -613,6 +654,7 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -625,6 +667,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -634,6 +677,7 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", @@ -645,6 +689,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@emnapi/wasi-threads": "1.1.0", @@ -656,6 +701,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -666,6 +712,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -675,13 +722,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -700,6 +749,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -709,6 +759,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", @@ -723,6 +774,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -733,6 +785,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -745,6 +798,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" }, @@ -752,23 +806,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "node_modules/@eslint/core": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", - "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -781,6 +824,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -804,16 +848,28 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -822,10 +878,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "version": "9.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", + "integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -838,6 +895,7 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -847,6 +905,7 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" @@ -855,23 +914,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -881,6 +929,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" @@ -894,6 +943,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -907,6 +957,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -920,6 +971,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -937,6 +989,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.4" }, @@ -949,6 +1002,7 @@ "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", @@ -965,6 +1019,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" } @@ -974,6 +1029,7 @@ "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" @@ -987,6 +1043,7 @@ "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" @@ -1000,6 +1057,7 @@ "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" }, @@ -1012,6 +1070,7 @@ "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" }, @@ -1027,6 +1086,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" }, @@ -1039,6 +1099,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" } @@ -1047,13 +1108,15 @@ "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/@istanbuljs/schema": { "version": "0.1.3", "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" } @@ -1063,6 +1126,7 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -1080,6 +1144,7 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "30.2.0", "@jest/pattern": "30.0.1", @@ -1127,6 +1192,7 @@ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1136,6 +1202,7 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", @@ -1151,6 +1218,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, + "license": "MIT", "dependencies": { "expect": "30.2.0", "jest-snapshot": "30.2.0" @@ -1164,6 +1232,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0" }, @@ -1176,6 +1245,7 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", @@ -1193,6 +1263,7 @@ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1202,6 +1273,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", "@jest/expect": "30.2.0", @@ -1217,6 +1289,7 @@ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "jest-regex-util": "30.0.1" @@ -1230,6 +1303,7 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "30.2.0", @@ -1272,6 +1346,7 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -1284,6 +1359,7 @@ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "chalk": "^4.1.2", @@ -1299,6 +1375,7 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "callsites": "^3.1.0", @@ -1313,6 +1390,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "30.2.0", "@jest/types": "30.2.0", @@ -1328,6 +1406,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", @@ -1343,6 +1422,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/types": "30.2.0", @@ -1369,6 +1449,7 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", @@ -1387,6 +1468,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -1397,6 +1479,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -1407,6 +1490,7 @@ "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" } @@ -1415,13 +1499,15 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1430,13 +1516,15 @@ "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" }, "node_modules/@napi-rs/nice": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">= 10" @@ -1473,6 +1561,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -1489,6 +1578,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -1505,6 +1595,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1521,6 +1612,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1537,6 +1629,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1553,6 +1646,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1569,6 +1663,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1585,6 +1680,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1601,6 +1697,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1617,6 +1714,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1633,6 +1731,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1649,6 +1748,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1665,6 +1765,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1681,6 +1782,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1697,6 +1799,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1713,6 +1816,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1729,6 +1833,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1742,6 +1847,7 @@ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@emnapi/core": "^1.4.3", @@ -1754,6 +1860,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, + "license": "MIT", "engines": { "node": "^14.21.3 || >=16" }, @@ -1766,6 +1873,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1779,6 +1887,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -1788,6 +1897,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1801,6 +1911,7 @@ "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, + "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -1816,13 +1927,15 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@npmcli/fs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -1835,6 +1948,7 @@ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, + "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.5" } @@ -1844,6 +1958,7 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -1854,6 +1969,7 @@ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -1866,6 +1982,7 @@ "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", "dev": true, + "license": "AGPL-3.0", "dependencies": { "async": "~3.2.0", "chalk": "~3.0.0", @@ -1886,6 +2003,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1898,13 +2016,15 @@ "version": "1.8.36", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@pm2/agent/node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1922,6 +2042,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -1934,6 +2055,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -1948,13 +2070,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@pm2/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", "dev": true, + "license": "MIT", "bin": { "blessed": "bin/tput.js" }, @@ -1967,6 +2091,7 @@ "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", "dev": true, + "license": "Apache-2", "dependencies": { "async": "~2.6.1", "debug": "~4.3.1", @@ -1986,6 +2111,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } @@ -1995,6 +2121,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2011,13 +2138,15 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@pm2/io/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==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -2030,6 +2159,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2044,19 +2174,22 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@pm2/io/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@pm2/js-api": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", "dev": true, + "license": "Apache-2", "dependencies": { "async": "^2.6.3", "debug": "~4.3.1", @@ -2073,6 +2206,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } @@ -2082,6 +2216,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2098,13 +2233,15 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@pm2/pm2-version-check": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.1" } @@ -2114,6 +2251,7 @@ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -2134,7 +2272,8 @@ "version": "6.18.0", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", - "devOptional": true, + "dev": true, + "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", @@ -2146,14 +2285,16 @@ "version": "6.18.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", - "devOptional": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", - "devOptional": true, + "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", @@ -2165,13 +2306,15 @@ "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", - "devOptional": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", - "devOptional": true, + "dev": true, + "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", @@ -2182,7 +2325,8 @@ "version": "6.18.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", - "devOptional": true, + "dev": true, + "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0" } @@ -2191,19 +2335,22 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true + "hasInstallScript": true, + "license": "Apache-2.0" }, "node_modules/@sinclair/typebox": { "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -2216,6 +2363,7 @@ "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" } @@ -2225,6 +2373,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" } @@ -2233,6 +2382,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" @@ -2242,13 +2392,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/@swc/cli": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, + "license": "MIT", "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -2284,6 +2436,7 @@ "integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -2324,6 +2477,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2340,6 +2494,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2356,6 +2511,7 @@ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2372,6 +2528,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2388,6 +2545,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2404,6 +2562,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2420,6 +2579,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2436,6 +2596,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2452,6 +2613,7 @@ "ia32" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2468,6 +2630,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2480,13 +2643,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } @@ -2496,6 +2661,7 @@ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, + "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.1" }, @@ -2508,6 +2674,7 @@ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", @@ -2525,43 +2692,50 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -2572,6 +2746,7 @@ "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", @@ -2585,6 +2760,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } @@ -2594,6 +2770,7 @@ "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" @@ -2604,6 +2781,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" } @@ -2613,6 +2791,7 @@ "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2622,6 +2801,7 @@ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -2632,6 +2812,7 @@ "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/node": "*" @@ -2642,6 +2823,7 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2651,6 +2833,7 @@ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, + "license": "MIT", "peerDependencies": { "@types/express": "*" } @@ -2659,13 +2842,15 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2674,13 +2859,15 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -2692,6 +2879,7 @@ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2704,6 +2892,7 @@ "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.7.tgz", "integrity": "sha512-YSQBkTwZepklRez0wgsljeewMytGNKgBAZR1YbmE0X49+elqkZ+fr/gvB407wL9Dl7a/Kv3W04yJueRmEHytBw==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -2712,25 +2901,29 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true + "dev": true, + "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": "*" } @@ -2740,6 +2933,7 @@ "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": "*" } @@ -2749,6 +2943,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^30.0.0", "pretty-format": "^30.0.0" @@ -2757,13 +2952,15 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, + "license": "MIT", "dependencies": { "@types/ms": "*", "@types/node": "*" @@ -2773,19 +2970,22 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", - "dev": true + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/@types/morgan": { "version": "1.9.10", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2794,13 +2994,15 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "24.9.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } @@ -2809,28 +3011,32 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true + "dev": true, + "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -2838,10 +3044,11 @@ } }, "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -2851,13 +3058,15 @@ "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/superagent": { "version": "8.1.9", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", @@ -2870,6 +3079,7 @@ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, + "license": "MIT", "dependencies": { "@types/methods": "^1.1.4", "@types/superagent": "^8.1.0" @@ -2879,13 +3089,15 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/swagger-ui-express": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/serve-static": "*" @@ -2894,18 +3106,21 @@ "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" }, "node_modules/@types/validator": { "version": "13.15.4", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.4.tgz", - "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==" + "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==", + "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.34", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -2914,13 +3129,15 @@ "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/@typescript-eslint/eslint-plugin": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.2", @@ -2945,20 +3162,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -2983,6 +3192,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.2", "@typescript-eslint/types": "^8.46.2", @@ -3004,6 +3214,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2" @@ -3021,6 +3232,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3037,6 +3249,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", @@ -3061,6 +3274,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3074,6 +3288,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.46.2", "@typescript-eslint/tsconfig-utils": "8.46.2", @@ -3102,6 +3317,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.2", @@ -3125,6 +3341,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" @@ -3142,6 +3359,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3153,7 +3371,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", @@ -3163,6 +3382,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3176,6 +3396,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3189,6 +3410,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3202,6 +3424,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3215,6 +3438,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -3228,6 +3452,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3241,6 +3466,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3254,6 +3480,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3267,6 +3494,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3280,6 +3508,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3293,6 +3522,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3306,6 +3536,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3319,6 +3550,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3332,6 +3564,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3345,6 +3578,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3358,6 +3592,7 @@ "wasm32" ], "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" @@ -3374,6 +3609,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3387,6 +3623,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3400,6 +3637,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3410,6 +3648,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", "dev": true, + "license": "MIT", "dependencies": { "file-type": "^20.5.0" }, @@ -3422,6 +3661,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.1.1", "isexe": "^2.0.0" @@ -3435,6 +3675,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", "dev": true, + "license": "MIT", "dependencies": { "@xhmikosr/bin-check": "^7.1.0", "@xhmikosr/downloader": "^15.2.0", @@ -3450,6 +3691,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", "dev": true, + "license": "MIT", "dependencies": { "@xhmikosr/decompress-tar": "^8.1.0", "@xhmikosr/decompress-tarbz2": "^8.1.0", @@ -3467,6 +3709,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", "dev": true, + "license": "MIT", "dependencies": { "file-type": "^20.5.0", "is-stream": "^2.0.1", @@ -3481,6 +3724,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", "dev": true, + "license": "MIT", "dependencies": { "@xhmikosr/decompress-tar": "^8.0.1", "file-type": "^20.5.0", @@ -3497,6 +3741,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", "dev": true, + "license": "MIT", "dependencies": { "@xhmikosr/decompress-tar": "^8.0.1", "file-type": "^20.5.0", @@ -3511,6 +3756,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", "dev": true, + "license": "MIT", "dependencies": { "file-type": "^20.5.0", "get-stream": "^6.0.1", @@ -3525,6 +3771,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", "dev": true, + "license": "MIT", "dependencies": { "@xhmikosr/archive-type": "^7.1.0", "@xhmikosr/decompress": "^10.2.0", @@ -3545,6 +3792,7 @@ "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", "dev": true, + "license": "MIT", "dependencies": { "arch": "^3.0.0" }, @@ -3557,6 +3805,7 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, + "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -3565,6 +3814,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -3573,21 +3823,11 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/accepts/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3597,6 +3837,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3609,6 +3850,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3618,6 +3860,7 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^8.11.0" }, @@ -3630,6 +3873,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } @@ -3639,6 +3883,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3654,13 +3899,15 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/amp-message": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", "dev": true, + "license": "MIT", "dependencies": { "amp": "0.3.1" } @@ -3670,6 +3917,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3679,6 +3927,7 @@ "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" }, @@ -3694,6 +3943,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3706,6 +3956,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -3721,6 +3972,7 @@ "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -3730,6 +3982,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" @@ -3756,24 +4009,28 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3782,13 +4039,15 @@ "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/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -3799,19 +4058,22 @@ "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "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/b4a": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, + "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" }, @@ -3826,6 +4088,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", @@ -3847,6 +4110,10 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -3863,6 +4130,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, + "license": "MIT", "dependencies": { "@types/babel__core": "^7.20.5" }, @@ -3875,6 +4143,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -3901,6 +4170,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, + "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "30.2.0", "babel-preset-current-node-syntax": "^1.2.0" @@ -3915,13 +4185,15 @@ "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/bare-events": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.1.tgz", "integrity": "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==", "dev": true, + "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" }, @@ -3949,13 +4221,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.8.22", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==", "dev": true, + "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -3964,6 +4238,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" }, @@ -3974,13 +4249,15 @@ "node_modules/basic-auth/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/basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -3990,6 +4267,7 @@ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" @@ -4003,6 +4281,7 @@ "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "find-versions": "^5.0.0" @@ -4019,6 +4298,7 @@ "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", "dev": true, + "license": "MIT", "dependencies": { "bin-version": "^6.0.0", "semver": "^7.5.3", @@ -4036,6 +4316,7 @@ "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" }, @@ -4047,12 +4328,14 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -4068,54 +4351,12 @@ "node": ">=18" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4125,6 +4366,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -4151,6 +4393,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4170,6 +4413,7 @@ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -4182,6 +4426,7 @@ "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" } @@ -4205,6 +4450,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -4215,6 +4461,7 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -4222,18 +4469,21 @@ "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==", - "dev": true + "dev": true, + "license": "MIT" }, "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" } @@ -4242,7 +4492,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", @@ -4266,11 +4517,28 @@ } } }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "devOptional": true, + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -4278,11 +4546,26 @@ "url": "https://dotenvx.com" } }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", @@ -4305,13 +4588,15 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" } @@ -4321,6 +4606,7 @@ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", @@ -4338,6 +4624,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -4350,6 +4637,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -4364,13 +4652,15 @@ "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4380,6 +4670,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4402,13 +4693,15 @@ "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==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4425,6 +4718,7 @@ "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" } @@ -4433,13 +4727,15 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", - "dev": true + "dev": true, + "license": "MIT/X11" }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", "dependencies": { "readdirp": "^4.0.1" }, @@ -4455,6 +4751,20 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } @@ -4470,6 +4780,7 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } @@ -4478,7 +4789,8 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "consola": "^3.2.3" } @@ -4487,17 +4799,20 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "license": "MIT", "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", @@ -4509,6 +4824,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" }, @@ -4536,6 +4852,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4549,6 +4866,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" @@ -4565,6 +4883,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, + "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" @@ -4581,6 +4900,7 @@ "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", @@ -4595,6 +4915,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4603,13 +4924,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cliui/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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4619,6 +4942,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4633,6 +4957,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4645,6 +4970,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", @@ -4662,6 +4988,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" @@ -4671,12 +4998,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/color": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", + "license": "MIT", "dependencies": { "color-convert": "^3.0.1", "color-string": "^2.0.0" @@ -4690,6 +5019,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4701,12 +5031,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/color-string": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", + "license": "MIT", "dependencies": { "color-name": "^2.0.0" }, @@ -4718,6 +5050,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "license": "MIT", "engines": { "node": ">=12.20" } @@ -4726,6 +5059,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", + "license": "MIT", "dependencies": { "color-name": "^2.0.0" }, @@ -4737,6 +5071,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "license": "MIT", "engines": { "node": ">=12.20" } @@ -4745,13 +5080,15 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "dev": true, + "license": "MIT" }, "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==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4764,6 +5101,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -4773,6 +5111,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" } @@ -4781,6 +5120,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -4792,6 +5132,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -4809,6 +5150,7 @@ "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" } @@ -4816,24 +5158,28 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "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/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "devOptional": true, + "dev": true, + "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -4843,6 +5189,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -4854,6 +5201,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" } @@ -4862,12 +5210,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.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4876,6 +5226,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.6" @@ -4887,18 +5238,21 @@ "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/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" @@ -4911,19 +5265,22 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/croner": { "version": "4.1.97", "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, + "license": "MIT", "dependencies": { "@epic-web/invariant": "^1.0.0", "cross-spawn": "^7.0.6" @@ -4941,6 +5298,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4954,13 +5312,15 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } @@ -4969,12 +5329,14 @@ "version": "1.11.15", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -4992,6 +5354,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -5007,6 +5370,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5019,6 +5383,7 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, + "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -5032,13 +5397,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5047,7 +5414,8 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "devOptional": true, + "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" } @@ -5057,6 +5425,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -5069,6 +5438,7 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -5077,13 +5447,15 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, + "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -5098,6 +5470,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" } @@ -5106,6 +5479,7 @@ "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" } @@ -5114,13 +5488,15 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/detect-newline": { "version": "3.1.0", "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" } @@ -5130,6 +5506,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" @@ -5140,6 +5517,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -5149,6 +5527,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -5160,6 +5539,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -5171,6 +5551,7 @@ "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -5178,71 +5559,32 @@ "url": "https://dotenvx.com" } }, - "node_modules/dotenv-cli": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", - "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "dotenv": "^17.1.0", - "dotenv-expand": "^12.0.0", - "minimist": "^1.2.6" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, - "bin": { - "dotenv": "cli.js" + "engines": { + "node": ">= 0.4" } }, - "node_modules/dotenv-expand": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", - "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ecdsa-sig-formatter": { - "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": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "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" } @@ -5250,13 +5592,15 @@ "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/effect": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" @@ -5266,13 +5610,15 @@ "version": "1.5.244", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", - "dev": true + "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" }, @@ -5284,13 +5630,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/empathic": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "devOptional": true, + "dev": true, + "license": "MIT", "engines": { "node": ">=14" } @@ -5298,12 +5646,14 @@ "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "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" } @@ -5313,29 +5663,18 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1" }, @@ -5348,6 +5687,7 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5356,6 +5696,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.0.tgz", "integrity": "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==", + "license": "MIT", "dependencies": { "tslib": "2.8.1" }, @@ -5368,6 +5709,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -5379,13 +5721,15 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -5394,6 +5738,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5402,6 +5747,7 @@ "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" } @@ -5410,6 +5756,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -5422,6 +5769,7 @@ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -5437,6 +5785,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5444,13 +5793,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": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5463,6 +5814,7 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -5484,25 +5836,27 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", + "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.0", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5553,6 +5907,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5568,6 +5923,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" @@ -5598,6 +5954,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5614,6 +5971,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5626,6 +5984,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5636,6 +5995,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5643,11 +6003,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5660,6 +6031,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -5677,6 +6049,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5689,6 +6062,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" @@ -5702,6 +6076,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5714,6 +6089,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5726,6 +6102,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -5734,6 +6111,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -5742,6 +6120,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" } @@ -5750,19 +6129,22 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bare-events": "^2.7.0" } @@ -5772,6 +6154,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", @@ -5795,6 +6178,7 @@ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -5804,6 +6188,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", @@ -5820,12 +6205,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -5867,6 +6254,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -5878,53 +6266,24 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", "engines": { "node": ">=6.6.0" } }, - "node_modules/express/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/exsolve": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "^1.28.0" }, @@ -5937,6 +6296,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, + "license": "MIT", "dependencies": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -5950,6 +6310,7 @@ "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.14.0" } @@ -5958,7 +6319,7 @@ "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "devOptional": true, + "dev": true, "funding": [ { "type": "individual", @@ -5969,6 +6330,7 @@ "url": "https://opencollective.com/fast-check" } ], + "license": "MIT", "dependencies": { "pure-rand": "^6.1.0" }, @@ -5980,7 +6342,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "individual", @@ -5990,31 +6352,36 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -6031,6 +6398,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" }, @@ -6042,31 +6410,36 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", - "dev": true + "dev": true, + "license": "MIT" }, "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-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "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/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -6076,6 +6449,7 @@ "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" } @@ -6084,24 +6458,46 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -6113,6 +6509,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", "dependencies": { "moment": "^2.29.1" } @@ -6122,6 +6519,7 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", "dev": true, + "license": "MIT", "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", @@ -6140,6 +6538,7 @@ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -6152,6 +6551,7 @@ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, + "license": "MIT", "dependencies": { "filename-reserved-regex": "^3.0.0" }, @@ -6167,6 +6567,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6178,6 +6579,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -6195,6 +6597,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -6211,6 +6614,7 @@ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", "dev": true, + "license": "MIT", "dependencies": { "semver-regex": "^4.0.5" }, @@ -6226,6 +6630,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -6238,12 +6643,14 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -6256,6 +6663,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -6270,6 +6678,7 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -6286,6 +6695,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -6298,6 +6708,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6314,15 +6725,40 @@ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.17" } }, + "node_modules/form-data/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, + "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", @@ -6339,6 +6775,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" } @@ -6347,6 +6784,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6356,6 +6794,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -6366,7 +6805,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==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -6374,6 +6814,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -6386,6 +6827,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" } @@ -6395,6 +6837,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" } @@ -6404,6 +6847,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.*" } @@ -6413,6 +6857,7 @@ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6424,6 +6869,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -6448,6 +6894,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" } @@ -6456,6 +6903,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -6469,6 +6917,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" }, @@ -6481,6 +6930,7 @@ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, + "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -6493,6 +6943,7 @@ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, + "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -6506,7 +6957,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -6523,19 +6975,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/git-sha1": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -6556,6 +7011,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -6568,6 +7024,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6580,6 +7037,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6595,10 +7053,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6611,6 +7080,7 @@ "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", @@ -6635,19 +7105,22 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -6669,6 +7142,7 @@ "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" } @@ -6678,6 +7152,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6686,6 +7161,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6698,6 +7174,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -6712,6 +7189,7 @@ "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" }, @@ -6723,6 +7201,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", "engines": { "node": ">=18.0.0" } @@ -6731,6 +7210,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "license": "ISC", "dependencies": { "lodash": "^4.17.12", "type-is": "^1.6.12" @@ -6739,22 +7219,68 @@ "node": ">=0.10.0" } }, + "node_modules/hpp/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/hpp/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/hpp/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/hpp/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/html-escaper": { "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-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "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", @@ -6770,6 +7296,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" } @@ -6779,6 +7306,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -6792,6 +7320,7 @@ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, + "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" @@ -6805,6 +7334,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -6818,6 +7348,7 @@ "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" } @@ -6827,6 +7358,7 @@ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, + "license": "MIT", "bin": { "husky": "bin.js" }, @@ -6838,12 +7370,12 @@ } }, "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==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -6867,13 +7399,15 @@ "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", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -6882,13 +7416,15 @@ "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-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6905,6 +7441,7 @@ "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" @@ -6924,6 +7461,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -6933,6 +7471,7 @@ "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" @@ -6941,19 +7480,22 @@ "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/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/inspect-with-kind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", "dev": true, + "license": "ISC", "dependencies": { "kind-of": "^6.0.2" } @@ -6963,6 +7505,7 @@ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -6971,6 +7514,7 @@ "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" } @@ -6979,13 +7523,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" }, @@ -6998,6 +7544,7 @@ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -7013,6 +7560,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" } @@ -7022,6 +7570,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, + "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.1" }, @@ -7037,6 +7586,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" } @@ -7046,6 +7596,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" }, @@ -7058,6 +7609,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -7067,6 +7619,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7074,12 +7627,14 @@ "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -7091,13 +7646,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "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" } @@ -7107,6 +7664,7 @@ "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.23.9", "@babel/parser": "^7.23.9", @@ -7123,6 +7681,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", @@ -7137,6 +7696,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", @@ -7151,6 +7711,7 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -7164,6 +7725,7 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -7179,6 +7741,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -7205,6 +7768,7 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.1.1", "jest-util": "30.2.0", @@ -7219,6 +7783,7 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", "@jest/expect": "30.2.0", @@ -7250,6 +7815,7 @@ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "30.2.0", "@jest/test-result": "30.2.0", @@ -7282,6 +7848,7 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", @@ -7333,6 +7900,7 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, + "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.1.0", @@ -7348,6 +7916,7 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, + "license": "MIT", "dependencies": { "detect-newline": "^3.1.0" }, @@ -7360,6 +7929,7 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", @@ -7376,6 +7946,7 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", @@ -7394,6 +7965,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -7418,6 +7990,7 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "pretty-format": "30.2.0" @@ -7431,6 +8004,7 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", @@ -7446,6 +8020,7 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.2.0", @@ -7466,6 +8041,7 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -7480,6 +8056,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" }, @@ -7497,6 +8074,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -7506,6 +8084,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", @@ -7525,6 +8104,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, + "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", "jest-snapshot": "30.2.0" @@ -7538,6 +8118,7 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "30.2.0", "@jest/environment": "30.2.0", @@ -7571,6 +8152,7 @@ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", @@ -7604,6 +8186,7 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", @@ -7636,6 +8219,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -7653,6 +8237,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7665,6 +8250,7 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", @@ -7682,6 +8268,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -7694,6 +8281,7 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "30.2.0", "@jest/types": "30.2.0", @@ -7713,6 +8301,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", @@ -7729,6 +8318,7 @@ "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" }, @@ -7743,7 +8333,8 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, + "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -7753,6 +8344,7 @@ "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", "dev": true, + "license": "MIT", "dependencies": { "bodec": "^0.1.0", "culvert": "^0.1.2", @@ -7764,12 +8356,14 @@ "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": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -7782,6 +8376,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -7793,31 +8388,36 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "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-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/json5": { @@ -7825,6 +8425,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" }, @@ -7836,6 +8437,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", @@ -7857,6 +8459,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -7867,6 +8470,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" @@ -7877,6 +8481,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -7886,6 +8491,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7893,13 +8499,15 @@ "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", "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" } @@ -7909,6 +8517,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -7918,21 +8527,24 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.24", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz", - "integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==" + "version": "1.12.25", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.25.tgz", + "integrity": "sha512-u90tUu/SEF8b+RaDKCoW7ZNFDakyBtFlX1ex3J+VH+ElWes/UaitJLt/w4jGu8uAE41lltV/s+kMVtywcMEg7g==", + "license": "MIT" }, "node_modules/lines-and-columns": { "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/lint-staged": { "version": "16.2.6", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, + "license": "MIT", "dependencies": { "commander": "^14.0.1", "listr2": "^9.0.5", @@ -7957,6 +8569,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=20" } @@ -7966,6 +8579,7 @@ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", @@ -7983,6 +8597,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7994,13 +8609,15 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -8010,7 +8627,7 @@ "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/listr2/node_modules/wrap-ansi": { @@ -8018,6 +8635,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -8035,6 +8653,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -8048,77 +8667,91 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" }, "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.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "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.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "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/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -8138,6 +8771,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -8153,6 +8787,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -8164,13 +8799,15 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -8188,6 +8825,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -8204,6 +8842,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", @@ -8221,6 +8860,7 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -8233,6 +8873,7 @@ "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": "^3.0.2" } @@ -8242,6 +8883,7 @@ "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": { "semver": "^7.5.3" }, @@ -8256,13 +8898,15 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", @@ -8285,6 +8929,7 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8294,6 +8939,7 @@ "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" } @@ -8302,22 +8948,25 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -8329,13 +8978,15 @@ "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/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -8345,6 +8996,7 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8354,6 +9006,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -8367,6 +9020,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -8378,34 +9032,29 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/mime-types/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/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8415,6 +9064,7 @@ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -8427,6 +9077,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -8439,6 +9090,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8454,6 +9106,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8463,6 +9116,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -8472,6 +9126,7 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -8484,6 +9139,7 @@ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -8501,6 +9157,7 @@ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -8513,6 +9170,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -8524,13 +9182,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -8543,6 +9203,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -8554,13 +9215,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -8573,6 +9236,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -8584,13 +9248,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.1.2" }, @@ -8603,6 +9269,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -8614,12 +9281,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", "engines": { "node": "*" } @@ -8628,6 +9297,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", @@ -8643,6 +9313,7 @@ "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" } @@ -8650,12 +9321,14 @@ "node_modules/morgan/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -8666,19 +9339,22 @@ "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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/mylas": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -8692,6 +9368,7 @@ "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, + "license": "MIT", "engines": { "node": ">=20.17" }, @@ -8704,6 +9381,7 @@ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, + "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" }, @@ -8718,13 +9396,15 @@ "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/needle": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -8742,14 +9422,29 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/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==", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8758,13 +9453,15 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -8773,6 +9470,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" } @@ -8790,13 +9488,15 @@ "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/node-gyp": { "version": "11.5.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -8820,6 +9520,7 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -8831,6 +9532,7 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -8840,6 +9542,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -8854,19 +9557,22 @@ "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.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -8895,52 +9601,18 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/nodemon/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/nodemon/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "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, + "license": "MIT", "engines": { "node": ">=4" } @@ -8950,6 +9622,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8957,23 +9630,12 @@ "node": "*" } }, - "node_modules/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "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" }, @@ -8986,6 +9648,7 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, + "license": "ISC", "dependencies": { "abbrev": "^3.0.0" }, @@ -9001,6 +9664,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" } @@ -9010,6 +9674,7 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -9017,24 +9682,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-check-updates": { - "version": "19.1.2", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.2.tgz", - "integrity": "sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==", - "bin": { - "ncu": "build/cli.js", - "npm-check-updates": "build/cli.js" - }, - "engines": { - "node": ">=20.0.0", - "npm": ">=8.12.1" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "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" }, @@ -9046,7 +9699,8 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", @@ -9065,6 +9719,7 @@ "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" } @@ -9073,6 +9728,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -9081,6 +9737,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9092,12 +9749,14 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "devOptional": true + "dev": true, + "license": "MIT" }, "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" }, @@ -9109,6 +9768,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9117,6 +9777,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" } @@ -9125,6 +9786,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", "dependencies": { "fn.name": "1.x.x" } @@ -9134,6 +9796,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" }, @@ -9155,6 +9818,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -9172,6 +9836,7 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.20" } @@ -9181,6 +9846,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -9196,6 +9862,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -9211,6 +9878,7 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -9223,6 +9891,7 @@ "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" } @@ -9232,6 +9901,7 @@ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, + "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", @@ -9251,6 +9921,7 @@ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, + "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -9263,19 +9934,22 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -9288,6 +9962,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", @@ -9305,6 +9980,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" } @@ -9314,6 +9990,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" } @@ -9322,6 +9999,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" } @@ -9331,6 +10009,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -9339,13 +10018,15 @@ "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-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -9361,12 +10042,14 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -9377,6 +10060,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -9385,31 +10069,36 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -9422,6 +10111,7 @@ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, + "license": "MIT", "bin": { "pidtree": "bin/pidtree.js" }, @@ -9434,6 +10124,7 @@ "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.2.1" }, @@ -9446,6 +10137,7 @@ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -9455,6 +10147,7 @@ "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", "dev": true, + "license": "MIT", "optionalDependencies": { "@napi-rs/nice": "^1.0.1" } @@ -9464,6 +10157,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" }, @@ -9476,6 +10170,7 @@ "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" @@ -9489,6 +10184,7 @@ "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" }, @@ -9501,6 +10197,7 @@ "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" }, @@ -9516,6 +10213,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" }, @@ -9527,7 +10225,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", @@ -9539,6 +10238,7 @@ "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", "dev": true, + "license": "MIT", "dependencies": { "queue-lit": "^1.5.1" }, @@ -9551,6 +10251,7 @@ "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.13.tgz", "integrity": "sha512-1hS/adMgKoDpX4S1ichJW8SiGpex+oBSZK31dP1FSYOOGtaeuemXzhXPOCefmddgIY4K6v7uu+7xNPnmEnK3ag==", "dev": true, + "license": "AGPL-3.0", "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/blessed": "0.1.81", @@ -9600,6 +10301,7 @@ "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", "dev": true, + "license": "MIT", "dependencies": { "amp": "~0.3.1", "amp-message": "~0.1.1", @@ -9615,6 +10317,7 @@ "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.1" }, @@ -9627,6 +10330,7 @@ "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", "dev": true, + "license": "MIT", "dependencies": { "run-series": "^1.1.8", "tv4": "^1.3.0" @@ -9640,6 +10344,7 @@ "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", "dev": true, + "license": "MIT/X11", "dependencies": { "charm": "~0.1.1" } @@ -9649,6 +10354,7 @@ "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", "dev": true, + "license": "Apache", "optional": true, "dependencies": { "async": "^3.2.0", @@ -9663,6 +10369,7 @@ "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safe-buffer": "^5.2.1" @@ -9671,65 +10378,19 @@ "node": ">=8" } }, - "node_modules/pm2/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/pm2/node_modules/commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/pm2/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pm2/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "license": "MIT" }, "node_modules/pm2/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -9742,6 +10403,7 @@ "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" } @@ -9751,6 +10413,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -9761,6 +10424,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -9770,6 +10434,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -9785,6 +10450,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -9797,6 +10463,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", @@ -9811,6 +10478,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" }, @@ -9822,8 +10490,9 @@ "version": "6.18.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", - "devOptional": true, + "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" @@ -9848,6 +10517,7 @@ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, + "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -9857,6 +10527,7 @@ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -9870,6 +10541,7 @@ "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", "dev": true, + "license": "MIT", "dependencies": { "read": "^1.0.4" } @@ -9878,6 +10550,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" @@ -9891,6 +10564,7 @@ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", @@ -9910,6 +10584,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -9918,19 +10593,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pstree.remy": { "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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -9949,12 +10627,14 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -9970,6 +10650,7 @@ "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -9992,13 +10673,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -10010,6 +10693,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" } @@ -10018,6 +10702,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -10032,6 +10717,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -10047,7 +10733,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -10057,13 +10744,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, + "license": "ISC", "dependencies": { "mute-stream": "~0.0.4" }, @@ -10075,6 +10764,7 @@ "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", @@ -10085,28 +10775,30 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, - "engines": { - "node": ">= 14.18.0" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=8.10.0" } }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10116,6 +10808,7 @@ "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "module-details-from-path": "^1.0.3", @@ -10130,6 +10823,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", @@ -10149,13 +10843,15 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/resolve-cwd": { "version": "3.0.0", "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" }, @@ -10168,6 +10864,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" } @@ -10177,6 +10874,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -10186,6 +10884,7 @@ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -10195,6 +10894,7 @@ "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, + "license": "MIT", "dependencies": { "lowercase-keys": "^3.0.0" }, @@ -10210,6 +10910,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -10226,6 +10927,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" }, @@ -10241,6 +10943,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -10253,6 +10956,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -10262,6 +10966,7 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -10271,12 +10976,14 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -10307,6 +11014,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -10329,7 +11037,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -10348,12 +11057,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-stable-stringify": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -10361,19 +11072,22 @@ "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/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/seek-bzip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", "dev": true, + "license": "MIT", "dependencies": { "commander": "^6.0.0" }, @@ -10387,6 +11101,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -10395,6 +11110,7 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -10407,6 +11123,7 @@ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -10419,6 +11136,7 @@ "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -10433,6 +11151,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", @@ -10450,21 +11169,11 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -10478,13 +11187,15 @@ "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==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -10497,6 +11208,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10505,12 +11217,14 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -10529,6 +11243,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -10544,6 +11259,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -10561,6 +11277,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -10579,13 +11296,15 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "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" }, @@ -10598,6 +11317,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10607,6 +11327,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -10623,6 +11344,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -10635,6 +11357,7 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -10645,6 +11368,7 @@ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, + "license": "MIT", "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" @@ -10659,6 +11383,7 @@ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -10673,6 +11398,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, + "license": "MIT", "dependencies": { "is-plain-obj": "^1.0.0" }, @@ -10685,6 +11411,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, + "license": "MIT", "dependencies": { "sort-keys": "^1.0.0" }, @@ -10697,6 +11424,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">= 12" } @@ -10706,6 +11434,7 @@ "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" @@ -10716,6 +11445,7 @@ "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" } @@ -10724,13 +11454,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -10742,6 +11474,7 @@ "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", "engines": { "node": "*" } @@ -10751,6 +11484,7 @@ "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" }, @@ -10763,6 +11497,7 @@ "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" } @@ -10771,6 +11506,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -10780,6 +11516,7 @@ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, + "license": "MIT", "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", @@ -10790,6 +11527,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" } @@ -10799,6 +11537,7 @@ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } @@ -10808,6 +11547,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" @@ -10821,6 +11561,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10830,6 +11571,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10842,6 +11584,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -10860,6 +11603,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10874,6 +11618,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10882,13 +11627,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/string-width-cjs/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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10898,6 +11645,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10910,6 +11658,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -10926,6 +11675,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10938,6 +11688,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10947,6 +11698,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" } @@ -10956,6 +11708,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, + "license": "ISC", "dependencies": { "inspect-with-kind": "^1.0.5", "is-plain-obj": "^1.1.0" @@ -10966,6 +11719,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" } @@ -10975,6 +11729,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" }, @@ -10987,6 +11742,7 @@ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "dev": true, + "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0" }, @@ -11003,6 +11759,7 @@ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", @@ -11023,6 +11780,7 @@ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, + "license": "MIT", "dependencies": { "methods": "^1.1.2", "superagent": "^10.2.3" @@ -11036,6 +11794,7 @@ "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": "^4.0.0" }, @@ -11048,6 +11807,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" }, @@ -11059,6 +11819,7 @@ "version": "6.2.8", "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", "dependencies": { "commander": "6.2.0", "doctrine": "3.0.0", @@ -11078,6 +11839,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11087,6 +11849,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -11096,6 +11859,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11115,6 +11879,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" }, @@ -11126,6 +11891,7 @@ "version": "2.0.0-1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -11134,6 +11900,7 @@ "version": "10.0.3", "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "10.0.3" }, @@ -11142,9 +11909,10 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.29.5", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.5.tgz", - "integrity": "sha512-2zFnjONgLXlz8gLToRKvXHKJdqXF6UGgCmv65i8T6i/UrjDNyV1fIQ7FauZA40SaivlGKEvW2tw9XDyDhfcXqQ==", + "version": "5.30.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.1.tgz", + "integrity": "sha512-4mNAUM31sr52K3JcK9qiGbfsFKNh/dm3PkEe+F9FAM31YY/NoRYUgsR/L6d7LLFn6PgZXtBG2ygp8+7UnpUIPg==", + "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" } @@ -11153,6 +11921,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", "dependencies": { "swagger-ui-dist": ">=5.0.0" }, @@ -11168,6 +11937,7 @@ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, + "license": "MIT", "dependencies": { "@pkgr/core": "^0.2.9" }, @@ -11183,6 +11953,7 @@ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.11.tgz", "integrity": "sha512-K3Lto/2m3K2twmKHdgx5B+0in9qhXK4YnoT9rIlgwN/4v7OV5c8IjbeAUkuky/6VzCQC7iKCAqi8rZathCdjHg==", "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin", @@ -11210,6 +11981,7 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -11226,6 +11998,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -11237,6 +12010,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } @@ -11246,6 +12020,7 @@ "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", @@ -11260,6 +12035,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11271,6 +12047,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11291,6 +12068,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11303,6 +12081,7 @@ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } @@ -11310,13 +12089,52 @@ "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/tinyexec": { "version": "1.0.1", @@ -11373,13 +12191,15 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -11391,6 +12211,7 @@ "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" } @@ -11400,6 +12221,7 @@ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", "dev": true, + "license": "MIT", "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", @@ -11418,6 +12240,7 @@ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -11426,6 +12249,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", "engines": { "node": ">= 14.0.0" } @@ -11435,6 +12259,7 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.12" }, @@ -11447,6 +12272,7 @@ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", @@ -11499,6 +12325,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -11511,6 +12338,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11554,6 +12382,7 @@ "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", @@ -11599,6 +12428,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } @@ -11632,6 +12462,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -11646,6 +12477,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -11653,13 +12485,24 @@ "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==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tv4": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", "dev": true, + "license": [ + { + "type": "Public Domain", + "url": "http://geraintluff.github.io/tv4/LICENSE.txt" + }, + { + "type": "MIT", + "url": "http://jsonary.com/LICENSE.txt" + } + ], "engines": { "node": ">= 0.8.0" } @@ -11669,6 +12512,7 @@ "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "json-stringify-safe": "^5.0.1" @@ -11679,6 +12523,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -11691,6 +12536,7 @@ "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" } @@ -11700,6 +12546,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" }, @@ -11708,12 +12555,14 @@ } }, "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==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -11722,13 +12571,15 @@ "node_modules/typedi": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", - "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", + "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11742,6 +12593,7 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -11755,6 +12607,7 @@ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -11767,6 +12620,7 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -11776,19 +12630,22 @@ "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": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, + "license": "ISC", "dependencies": { "unique-slug": "^5.0.0" }, @@ -11801,6 +12658,7 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -11812,6 +12670,7 @@ "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" } @@ -11822,6 +12681,7 @@ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -11869,6 +12729,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -11885,6 +12746,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -11892,19 +12754,22 @@ "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/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 + "dev": true, + "license": "MIT" }, "node_modules/v8-to-istanbul": { "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", @@ -11915,9 +12780,10 @@ } }, "node_modules/validator": { - "version": "13.15.15", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", - "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "version": "13.15.20", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", + "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -11926,6 +12792,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" } @@ -11935,6 +12802,7 @@ "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", "dev": true, + "license": "Apache-2.0", "dependencies": { "async": "^2.6.3", "git-node-fs": "^1.0.0", @@ -11950,6 +12818,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } @@ -11959,6 +12828,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" } @@ -11968,6 +12838,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -11982,6 +12853,7 @@ "version": "3.18.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -12003,6 +12875,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "license": "MIT", "dependencies": { "file-stream-rotator": "^0.6.1", "object-hash": "^3.0.0", @@ -12020,6 +12893,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", @@ -12034,6 +12908,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12042,13 +12917,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -12067,6 +12944,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", @@ -12084,6 +12962,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12092,13 +12971,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi-cjs/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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12108,6 +12989,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -12122,6 +13004,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -12134,6 +13017,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -12144,13 +13028,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": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -12164,6 +13050,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -12176,6 +13063,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -12197,6 +13085,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -12205,13 +13094,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, + "license": "ISC", "bin": { "yaml": "bin.mjs" }, @@ -12224,6 +13115,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", @@ -12242,6 +13134,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" } @@ -12251,6 +13144,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12259,13 +13153,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/yargs/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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12275,6 +13171,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -12289,6 +13186,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -12301,6 +13199,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" @@ -12314,6 +13213,7 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -12323,6 +13223,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" }, @@ -12334,6 +13235,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", "dependencies": { "lodash.get": "^4.4.2", "lodash.isequal": "^4.5.0", @@ -12353,6 +13255,7 @@ "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", "optional": true, "engines": { "node": "^12.20.0 || >=14" diff --git a/package.json b/package.json index b3c5873..6f8b2da 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", - "npm-check-updates": "^19.1.2", "reflect-metadata": "^0.2.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", @@ -63,8 +62,7 @@ "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", "cross-env": "^10.1.0", - "dotenv-cli": "^11.0.0", - "eslint": "^9.38.0", + "eslint": "^9.39.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", "husky": "^9.1.7", From 99ab8871872c43c936348dd5a638248b94141acf Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 1 Nov 2025 12:52:47 +0200 Subject: [PATCH 011/210] remove db test from CI --- .github/workflows/ci.yml | 22 ++++----------------- Dockerfile | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ba2b9b..4fb147f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,20 +15,6 @@ jobs: tests: runs-on: ubuntu-latest - env: - NODE_ENV: test - DATABASE_URL: "postgresql://testuser:testpassword@localhost:5432/testdb" - - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: testuser - POSTGRES_PASSWORD: testpassword - POSTGRES_DB: testdb - ports: - - 5432:5432 - steps: - name: checkout code uses: actions/checkout@v4 @@ -44,10 +30,10 @@ jobs: - name: generate prisma client run: npx prisma generate - - name: run Prisma migrations - run: npx prisma migrate deploy - env: - DATABASE_URL: ${{ env.DATABASE_URL }} + # - name: run Prisma migrations + # run: npx prisma migrate deploy + # env: + # DATABASE_URL: ${{ env.DATABASE_URL }} - name: build app run: npm run build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9e53389 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# First stage: BUILD THE APP # + +# NodeJS Version 22 +FROM node:22-bullseye AS builder + +# Work to Dir +WORKDIR /app + +#copy package and prisma files +COPY package*.json ./ +COPY src/prisma ./src/prisma + +# Install Node Package +RUN npm ci --legacy-peer-deps + +# generate prisma client +RUN npx prisma generate + +# Copy rest of the app +COPY . . + + + +# second stage # + +FROM node:22-bullseye AS runner + +WORKDIR /app + +# copy only needed files +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/src/prisma ./src/prisma +COPY --from=builder /app/package*.json ./ + +# Set Env +ENV NODE_ENV=development + +EXPOSE 3000 + +# Cmd script +CMD ["npm", "run", "start"] \ No newline at end of file From 9bcda2651097c7385aca584f8fb9cceea74159d2 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 1 Nov 2025 13:53:20 +0200 Subject: [PATCH 012/210] fix: update package-lock.json syncing issue / simplify CI for setup --- .github/workflows/ci.yml | 25 +- package-lock.json | 3031 +++----------------------------------- 2 files changed, 228 insertions(+), 2828 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fb147f..ab3156e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,40 +5,27 @@ on: branches: - main - dev - pull_request: branches: - main - dev jobs: - tests: + setup-check: runs-on: ubuntu-latest steps: - - name: checkout code + - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' + cache: 'npm' - - name: install dependencies + - name: Install dependencies run: npm ci - - name: generate prisma client - run: npx prisma generate - - # - name: run Prisma migrations - # run: npx prisma migrate deploy - # env: - # DATABASE_URL: ${{ env.DATABASE_URL }} - - - name: build app - run: npm run build - - - name: run tests - run: npm test - - + - name: Generate Prisma client + run: npx prisma generate \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3faa179..56885c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,8 +71,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", - "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", @@ -83,8 +81,6 @@ }, "node_modules/@apidevtools/openapi-schemas": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", - "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "license": "MIT", "engines": { "node": ">=10" @@ -92,14 +88,10 @@ }, "node_modules/@apidevtools/swagger-methods": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", @@ -115,8 +107,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { @@ -130,8 +120,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -140,8 +128,6 @@ }, "node_modules/@babel/core": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", "dependencies": { @@ -171,8 +157,6 @@ }, "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": { @@ -181,8 +165,6 @@ }, "node_modules/@babel/generator": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -198,8 +180,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { @@ -215,8 +195,6 @@ }, "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": { @@ -225,8 +203,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { @@ -235,8 +211,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { @@ -249,8 +223,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { @@ -267,8 +239,6 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -277,8 +247,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -287,8 +255,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -297,8 +263,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -307,8 +271,6 @@ }, "node_modules/@babel/helpers": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { @@ -321,8 +283,6 @@ }, "node_modules/@babel/parser": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -337,8 +297,6 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "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": { @@ -350,8 +308,6 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "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": { @@ -363,8 +319,6 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "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": { @@ -376,8 +330,6 @@ }, "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": { @@ -392,8 +344,6 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", "dependencies": { @@ -408,8 +358,6 @@ }, "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": { @@ -421,8 +369,6 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "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": { @@ -434,8 +380,6 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "license": "MIT", "dependencies": { @@ -450,8 +394,6 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "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": { @@ -463,8 +405,6 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "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": { @@ -476,8 +416,6 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "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": { @@ -489,8 +427,6 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "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": { @@ -502,8 +438,6 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "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": { @@ -515,8 +449,6 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "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": { @@ -528,8 +460,6 @@ }, "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": { @@ -544,8 +474,6 @@ }, "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": { @@ -560,8 +488,6 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -576,8 +502,6 @@ }, "node_modules/@babel/template": { "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { @@ -591,8 +515,6 @@ }, "node_modules/@babel/traverse": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -610,8 +532,6 @@ }, "node_modules/@babel/types": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { @@ -624,15 +544,11 @@ }, "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, "license": "MIT" }, "node_modules/@borewit/text-codec": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", "dev": true, "license": "MIT", "funding": { @@ -642,8 +558,6 @@ }, "node_modules/@colors/colors": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { "node": ">=0.1.90" @@ -651,8 +565,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -664,8 +576,6 @@ }, "node_modules/@cspotcode/source-map-support/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, "license": "MIT", "dependencies": { @@ -675,8 +585,6 @@ }, "node_modules/@dabh/diagnostics": { "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", "license": "MIT", "dependencies": { "@so-ric/colorspace": "^1.1.6", @@ -684,51 +592,13 @@ "kuler": "^2.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", - "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", - "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@epic-web/invariant": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", - "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", "dev": true, "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -746,8 +616,6 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -756,8 +624,6 @@ }, "node_modules/@eslint/config-array": { "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -771,8 +637,6 @@ }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -782,8 +646,6 @@ }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -795,8 +657,6 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -808,8 +668,6 @@ }, "node_modules/@eslint/core": { "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -821,8 +679,6 @@ }, "node_modules/@eslint/eslintrc": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -845,8 +701,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -856,8 +710,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -866,8 +718,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -879,8 +729,6 @@ }, "node_modules/@eslint/js": { "version": "9.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", - "integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", "dev": true, "license": "MIT", "engines": { @@ -892,8 +740,6 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -902,8 +748,6 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -916,8 +760,6 @@ }, "node_modules/@humanfs/core": { "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -926,8 +768,6 @@ }, "node_modules/@humanfs/node": { "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -940,8 +780,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -954,8 +792,6 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -968,8 +804,6 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -986,8 +820,6 @@ }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, "license": "ISC", "dependencies": { @@ -999,8 +831,6 @@ }, "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": { @@ -1016,8 +846,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -1026,8 +854,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/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": { @@ -1040,8 +866,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/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": { @@ -1054,8 +878,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/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": { @@ -1067,8 +889,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/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": { @@ -1083,8 +903,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "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": { @@ -1096,8 +914,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -1106,15 +922,11 @@ }, "node_modules/@istanbuljs/load-nyc-config/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, "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { @@ -1123,8 +935,6 @@ }, "node_modules/@jest/console": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1141,8 +951,6 @@ }, "node_modules/@jest/core": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1189,8 +997,6 @@ }, "node_modules/@jest/diff-sequences": { "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, "license": "MIT", "engines": { @@ -1199,8 +1005,6 @@ }, "node_modules/@jest/environment": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", "dependencies": { @@ -1215,8 +1019,6 @@ }, "node_modules/@jest/expect": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1229,8 +1031,6 @@ }, "node_modules/@jest/expect-utils": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, "license": "MIT", "dependencies": { @@ -1242,8 +1042,6 @@ }, "node_modules/@jest/fake-timers": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", "dependencies": { @@ -1260,8 +1058,6 @@ }, "node_modules/@jest/get-type": { "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "dev": true, "license": "MIT", "engines": { @@ -1270,8 +1066,6 @@ }, "node_modules/@jest/globals": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", "dependencies": { @@ -1286,8 +1080,6 @@ }, "node_modules/@jest/pattern": { "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, "license": "MIT", "dependencies": { @@ -1300,8 +1092,6 @@ }, "node_modules/@jest/reporters": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1343,8 +1133,6 @@ }, "node_modules/@jest/schemas": { "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1356,8 +1144,6 @@ }, "node_modules/@jest/snapshot-utils": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, "license": "MIT", "dependencies": { @@ -1372,8 +1158,6 @@ }, "node_modules/@jest/source-map": { "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { @@ -1387,8 +1171,6 @@ }, "node_modules/@jest/test-result": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { @@ -1403,8 +1185,6 @@ }, "node_modules/@jest/test-sequencer": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1419,8 +1199,6 @@ }, "node_modules/@jest/transform": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { @@ -1446,8 +1224,6 @@ }, "node_modules/@jest/types": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1465,8 +1241,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1476,8 +1250,6 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1487,8 +1259,6 @@ }, "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, "license": "MIT", "engines": { @@ -1497,15 +1267,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1515,14 +1281,10 @@ }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, "node_modules/@napi-rs/nice": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", - "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, "license": "MIT", "optional": true, @@ -1553,214 +1315,8 @@ "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, - "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", - "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", - "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", - "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", - "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", - "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", - "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", - "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", - "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", - "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", - "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", - "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@napi-rs/nice-linux-x64-gnu": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", - "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", - "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", "cpu": [ "x64" ], @@ -1774,91 +1330,8 @@ "node": ">= 10" } }, - "node_modules/@napi-rs/nice-openharmony-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", - "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", - "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", - "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", - "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -1870,8 +1343,6 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -1884,8 +1355,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -1894,8 +1363,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1908,8 +1375,6 @@ }, "node_modules/@npmcli/agent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "license": "ISC", "dependencies": { @@ -1925,15 +1390,11 @@ }, "node_modules/@npmcli/agent/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/fs": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { @@ -1945,8 +1406,6 @@ }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "license": "MIT", "dependencies": { @@ -1955,8 +1414,6 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -1966,8 +1423,6 @@ }, "node_modules/@pkgr/core": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -1979,8 +1434,6 @@ }, "node_modules/@pm2/agent": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", - "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", "dev": true, "license": "AGPL-3.0", "dependencies": { @@ -2000,8 +1453,6 @@ }, "node_modules/@pm2/agent/node_modules/chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "license": "MIT", "dependencies": { @@ -2014,15 +1465,11 @@ }, "node_modules/@pm2/agent/node_modules/dayjs": { "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", "dev": true, "license": "MIT" }, "node_modules/@pm2/agent/node_modules/debug": { "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2039,8 +1486,6 @@ }, "node_modules/@pm2/agent/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==", "dev": true, "license": "ISC", "dependencies": { @@ -2052,8 +1497,6 @@ }, "node_modules/@pm2/agent/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==", "dev": true, "license": "ISC", "dependencies": { @@ -2068,15 +1511,11 @@ }, "node_modules/@pm2/agent/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/@pm2/blessed": { "version": "0.1.81", - "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", "dev": true, "license": "MIT", "bin": { @@ -2088,8 +1527,6 @@ }, "node_modules/@pm2/io": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", - "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", "dev": true, "license": "Apache-2", "dependencies": { @@ -2108,8 +1545,6 @@ }, "node_modules/@pm2/io/node_modules/async": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "license": "MIT", "dependencies": { @@ -2118,8 +1553,6 @@ }, "node_modules/@pm2/io/node_modules/debug": { "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2136,15 +1569,11 @@ }, "node_modules/@pm2/io/node_modules/eventemitter2": { "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "dev": true, "license": "MIT" }, "node_modules/@pm2/io/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==", "dev": true, "license": "ISC", "dependencies": { @@ -2156,8 +1585,6 @@ }, "node_modules/@pm2/io/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==", "dev": true, "license": "ISC", "dependencies": { @@ -2172,22 +1599,16 @@ }, "node_modules/@pm2/io/node_modules/tslib": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@pm2/io/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/@pm2/js-api": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", - "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", "dev": true, "license": "Apache-2", "dependencies": { @@ -2203,8 +1624,6 @@ }, "node_modules/@pm2/js-api/node_modules/async": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "license": "MIT", "dependencies": { @@ -2213,8 +1632,6 @@ }, "node_modules/@pm2/js-api/node_modules/debug": { "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2231,8 +1648,6 @@ }, "node_modules/@pm2/js-api/node_modules/eventemitter2": { "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "dev": true, "license": "MIT" }, @@ -2248,8 +1663,6 @@ }, "node_modules/@prisma/client": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", - "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -2270,9 +1683,7 @@ }, "node_modules/@prisma/config": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", - "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", @@ -2283,16 +1694,12 @@ }, "node_modules/@prisma/debug": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", - "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", - "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -2304,16 +1711,12 @@ }, "node_modules/@prisma/engines-version": { "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", - "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", - "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0", @@ -2323,9 +1726,7 @@ }, "node_modules/@prisma/get-platform": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", - "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0" @@ -2333,22 +1734,16 @@ }, "node_modules/@scarf/scarf": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", "hasInstallScript": true, "license": "Apache-2.0" }, "node_modules/@sinclair/typebox": { "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "license": "MIT", "engines": { @@ -2360,8 +1755,6 @@ }, "node_modules/@sinonjs/commons": { "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": { @@ -2370,8 +1763,6 @@ }, "node_modules/@sinonjs/fake-timers": { "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2380,8 +1771,6 @@ }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", - "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", "license": "MIT", "dependencies": { "color": "^5.0.2", @@ -2390,15 +1779,11 @@ }, "node_modules/@standard-schema/spec": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@swc/cli": { "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", - "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, "license": "MIT", "dependencies": { @@ -2432,8 +1817,6 @@ }, "node_modules/@swc/core": { "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz", - "integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -2469,112 +1852,8 @@ } } }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.14.0.tgz", - "integrity": "sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.14.0.tgz", - "integrity": "sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.14.0.tgz", - "integrity": "sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.14.0.tgz", - "integrity": "sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.14.0.tgz", - "integrity": "sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/core-linux-x64-gnu": { "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.14.0.tgz", - "integrity": "sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.14.0.tgz", - "integrity": "sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==", "cpu": [ "x64" ], @@ -2588,68 +1867,13 @@ "node": ">=10" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.14.0.tgz", - "integrity": "sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.14.0.tgz", - "integrity": "sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.14.0.tgz", - "integrity": "sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2658,8 +1882,6 @@ }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "license": "MIT", "dependencies": { @@ -2671,8 +1893,6 @@ }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", "dev": true, "license": "MIT", "dependencies": { @@ -2690,61 +1910,36 @@ }, "node_modules/@tokenizer/token": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "dev": true, "license": "MIT" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, "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, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "license": "MIT" }, "node_modules/@types/babel__core": { "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": { @@ -2757,8 +1952,6 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -2767,8 +1960,6 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", - "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": { @@ -2778,8 +1969,6 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2788,8 +1977,6 @@ }, "node_modules/@types/bcrypt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2798,8 +1985,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -2809,8 +1994,6 @@ }, "node_modules/@types/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2820,8 +2003,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -2830,8 +2011,6 @@ }, "node_modules/@types/cookie-parser": { "version": "1.4.10", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", - "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2840,15 +2019,11 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, "license": "MIT", "dependencies": { @@ -2857,15 +2032,11 @@ }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", - "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2876,8 +2047,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "license": "MIT", "dependencies": { @@ -2889,8 +2058,6 @@ }, "node_modules/@types/hpp": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.7.tgz", - "integrity": "sha512-YSQBkTwZepklRez0wgsljeewMytGNKgBAZR1YbmE0X49+elqkZ+fr/gvB407wL9Dl7a/Kv3W04yJueRmEHytBw==", "dev": true, "license": "MIT", "dependencies": { @@ -2899,29 +2066,21 @@ }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true, "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "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, "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": { @@ -2930,8 +2089,6 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "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": { @@ -2940,8 +2097,6 @@ }, "node_modules/@types/jest": { "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { @@ -2951,14 +2106,10 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { @@ -2968,22 +2119,16 @@ }, "node_modules/@types/methods": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/@types/morgan": { "version": "1.9.10", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", - "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2992,15 +2137,11 @@ }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "24.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", - "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, "license": "MIT", "dependencies": { @@ -3009,22 +2150,16 @@ }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "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==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3033,8 +2168,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { @@ -3045,8 +2178,6 @@ }, "node_modules/@types/serve-static/node_modules/@types/send": { "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", "dependencies": { @@ -3056,15 +2187,11 @@ }, "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, "license": "MIT" }, "node_modules/@types/superagent": { "version": "8.1.9", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", - "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3076,8 +2203,6 @@ }, "node_modules/@types/supertest": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", - "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "license": "MIT", "dependencies": { @@ -3087,15 +2212,11 @@ }, "node_modules/@types/swagger-jsdoc": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", - "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", "dev": true, "license": "MIT" }, "node_modules/@types/swagger-ui-express": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", - "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", "dev": true, "license": "MIT", "dependencies": { @@ -3105,20 +2226,14 @@ }, "node_modules/@types/triple-beam": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, "node_modules/@types/validator": { "version": "13.15.4", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.4.tgz", - "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==", "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.34", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", - "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "dev": true, "license": "MIT", "dependencies": { @@ -3127,15 +2242,11 @@ }, "node_modules/@types/yargs-parser": { "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, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", "dev": true, "license": "MIT", "dependencies": { @@ -3164,8 +2275,6 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3189,8 +2298,6 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", "dev": true, "license": "MIT", "dependencies": { @@ -3211,8 +2318,6 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, "license": "MIT", "dependencies": { @@ -3229,8 +2334,6 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", "dev": true, "license": "MIT", "engines": { @@ -3246,8 +2349,6 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", "dev": true, "license": "MIT", "dependencies": { @@ -3271,8 +2372,6 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, "license": "MIT", "engines": { @@ -3285,8 +2384,6 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3314,8 +2411,6 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, "license": "MIT", "dependencies": { @@ -3338,8 +2433,6 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, "license": "MIT", "dependencies": { @@ -3356,8 +2449,6 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3369,211 +2460,11 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, "license": "ISC" }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -3584,69 +2475,8 @@ "linux" ] }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@xhmikosr/archive-type": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", - "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", "dev": true, "license": "MIT", "dependencies": { @@ -3658,8 +2488,6 @@ }, "node_modules/@xhmikosr/bin-check": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", - "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", "dev": true, "license": "MIT", "dependencies": { @@ -3672,8 +2500,6 @@ }, "node_modules/@xhmikosr/bin-wrapper": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", - "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", "dev": true, "license": "MIT", "dependencies": { @@ -3688,8 +2514,6 @@ }, "node_modules/@xhmikosr/decompress": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", - "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", "dev": true, "license": "MIT", "dependencies": { @@ -3706,8 +2530,6 @@ }, "node_modules/@xhmikosr/decompress-tar": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", - "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -3721,8 +2543,6 @@ }, "node_modules/@xhmikosr/decompress-tarbz2": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", - "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", "dev": true, "license": "MIT", "dependencies": { @@ -3738,8 +2558,6 @@ }, "node_modules/@xhmikosr/decompress-targz": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", - "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", "dev": true, "license": "MIT", "dependencies": { @@ -3753,8 +2571,6 @@ }, "node_modules/@xhmikosr/decompress-unzip": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", - "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", "dev": true, "license": "MIT", "dependencies": { @@ -3768,8 +2584,6 @@ }, "node_modules/@xhmikosr/downloader": { "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", - "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", "dev": true, "license": "MIT", "dependencies": { @@ -3789,8 +2603,6 @@ }, "node_modules/@xhmikosr/os-filter-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", - "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", "dev": true, "license": "MIT", "dependencies": { @@ -3802,8 +2614,6 @@ }, "node_modules/abbrev": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { @@ -3812,8 +2622,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -3825,8 +2633,6 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3834,8 +2640,6 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3847,8 +2651,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3857,8 +2659,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -3870,8 +2670,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -3880,8 +2678,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -3897,15 +2693,11 @@ }, "node_modules/amp": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", - "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", "dev": true, "license": "MIT" }, "node_modules/amp-message": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", - "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3914,8 +2706,6 @@ }, "node_modules/ansi-colors": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", "engines": { @@ -3924,8 +2714,6 @@ }, "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": { @@ -3940,8 +2728,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -3953,8 +2739,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3969,8 +2753,6 @@ }, "node_modules/ansis": { "version": "4.0.0-node10", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", - "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "dev": true, "license": "ISC", "engines": { @@ -3979,8 +2761,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -3991,10 +2771,21 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/arch": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", - "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", "dev": true, "funding": [ { @@ -4014,21 +2805,15 @@ }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { @@ -4037,15 +2822,11 @@ }, "node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "node_modules/ast-types": { "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4057,21 +2838,15 @@ }, "node_modules/async": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "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, "license": "MIT" }, "node_modules/b4a": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4085,8 +2860,6 @@ }, "node_modules/babel-jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { @@ -4107,8 +2880,6 @@ }, "node_modules/babel-plugin-istanbul": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -4127,8 +2898,6 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { @@ -4140,8 +2909,6 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -4167,8 +2934,6 @@ }, "node_modules/babel-preset-jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4184,14 +2949,10 @@ }, "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==", "license": "MIT" }, "node_modules/bare-events": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.1.tgz", - "integrity": "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4205,8 +2966,6 @@ }, "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==", "dev": true, "funding": [ { @@ -4226,8 +2985,6 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.8.22", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", - "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4236,8 +2993,6 @@ }, "node_modules/basic-auth": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" @@ -4248,14 +3003,10 @@ }, "node_modules/basic-auth/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==", "license": "MIT" }, "node_modules/basic-ftp": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "license": "MIT", "engines": { @@ -4264,8 +3015,6 @@ }, "node_modules/bcrypt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4278,8 +3027,6 @@ }, "node_modules/bin-version": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", - "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", "dev": true, "license": "MIT", "dependencies": { @@ -4295,8 +3042,6 @@ }, "node_modules/bin-version-check": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", - "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -4326,15 +3071,11 @@ }, "node_modules/bodec": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", "dev": true, "license": "MIT" }, "node_modules/body-parser": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -4353,8 +3094,6 @@ }, "node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4363,8 +3102,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -4376,8 +3113,6 @@ }, "node_modules/browserslist": { "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "dev": true, "funding": [ { @@ -4410,8 +3145,6 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", "dependencies": { @@ -4423,8 +3156,6 @@ }, "node_modules/bser": { "version": "2.1.1", - "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": { @@ -4433,8 +3164,6 @@ }, "node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -4458,8 +3187,6 @@ }, "node_modules/buffer-crc32": { "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { @@ -4468,21 +3195,15 @@ }, "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==", "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==", "dev": true, "license": "MIT" }, "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" @@ -4490,9 +3211,7 @@ }, "node_modules/c12": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.3", @@ -4517,27 +3236,9 @@ } } }, - "node_modules/c12/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -4546,24 +3247,8 @@ "url": "https://dotenvx.com" } }, - "node_modules/c12/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/cacache": { "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { @@ -4586,15 +3271,11 @@ }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/cacheable-lookup": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, "license": "MIT", "engines": { @@ -4603,8 +3284,6 @@ }, "node_modules/cacheable-request": { "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4622,8 +3301,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4635,8 +3312,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4651,14 +3326,10 @@ }, "node_modules/call-me-maybe": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -4667,8 +3338,6 @@ }, "node_modules/camelcase": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -4677,8 +3346,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001752", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", - "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", "dev": true, "funding": [ { @@ -4698,8 +3365,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -4715,8 +3380,6 @@ }, "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": { @@ -4725,16 +3388,14 @@ }, "node_modules/charm": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", "dev": true, "license": "MIT/X11" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -4748,21 +3409,6 @@ }, "node_modules/chownr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -4771,8 +3417,6 @@ }, "node_modules/ci-info": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -4787,9 +3431,7 @@ }, "node_modules/citty": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -4797,21 +3439,15 @@ }, "node_modules/cjs-module-lexer": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", "dev": true, "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.2", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", - "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "license": "MIT", "dependencies": { "@types/validator": "^13.11.8", @@ -4821,8 +3457,6 @@ }, "node_modules/cli-cursor": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { @@ -4837,8 +3471,6 @@ }, "node_modules/cli-tableau": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", - "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", "dev": true, "dependencies": { "chalk": "3.0.0" @@ -4849,8 +3481,6 @@ }, "node_modules/cli-tableau/node_modules/chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "license": "MIT", "dependencies": { @@ -4863,8 +3493,6 @@ }, "node_modules/cli-truncate": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { @@ -4880,8 +3508,6 @@ }, "node_modules/cli-truncate/node_modules/string-width": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, "license": "MIT", "dependencies": { @@ -4897,8 +3523,6 @@ }, "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": { @@ -4912,8 +3536,6 @@ }, "node_modules/cliui/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==", "dev": true, "license": "MIT", "engines": { @@ -4922,15 +3544,11 @@ }, "node_modules/cliui/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==", "dev": true, "license": "MIT" }, "node_modules/cliui/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==", "dev": true, "license": "MIT", "engines": { @@ -4939,8 +3557,6 @@ }, "node_modules/cliui/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==", "dev": true, "license": "MIT", "dependencies": { @@ -4954,8 +3570,6 @@ }, "node_modules/cliui/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==", "dev": true, "license": "MIT", "dependencies": { @@ -4967,8 +3581,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4985,8 +3597,6 @@ }, "node_modules/co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -4996,15 +3606,11 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, "node_modules/color": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", - "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", "license": "MIT", "dependencies": { "color-convert": "^3.0.1", @@ -5016,8 +3622,6 @@ }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5029,15 +3633,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/color-string": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", - "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5048,8 +3648,6 @@ }, "node_modules/color-string/node_modules/color-name": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", "license": "MIT", "engines": { "node": ">=12.20" @@ -5057,8 +3655,6 @@ }, "node_modules/color/node_modules/color-convert": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", - "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5069,8 +3665,6 @@ }, "node_modules/color/node_modules/color-name": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", "license": "MIT", "engines": { "node": ">=12.20" @@ -5078,15 +3672,11 @@ }, "node_modules/colorette": { "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5098,8 +3688,6 @@ }, "node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "license": "MIT", "engines": { @@ -5108,8 +3696,6 @@ }, "node_modules/component-emitter": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -5118,8 +3704,6 @@ }, "node_modules/compressible": { "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -5130,8 +3714,6 @@ }, "node_modules/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -5148,8 +3730,6 @@ }, "node_modules/compression/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" @@ -5157,28 +3737,20 @@ }, "node_modules/compression/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/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, "node_modules/confbox": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -5186,8 +3758,6 @@ }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5199,8 +3769,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" @@ -5208,15 +3776,11 @@ }, "node_modules/convert-source-map": { "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, "license": "MIT" }, "node_modules/cookie": { "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" @@ -5224,8 +3788,6 @@ }, "node_modules/cookie-parser": { "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { "cookie": "0.7.2", @@ -5237,21 +3799,15 @@ }, "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/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, "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", @@ -5263,22 +3819,16 @@ }, "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, "license": "MIT" }, "node_modules/croner": { "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", "dev": true, "license": "MIT" }, "node_modules/cross-env": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", - "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, "license": "MIT", "dependencies": { @@ -5295,8 +3845,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -5310,15 +3858,11 @@ }, "node_modules/culvert": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", "dev": true, "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "license": "MIT", "engines": { @@ -5327,15 +3871,11 @@ }, "node_modules/dayjs": { "version": "1.11.15", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", - "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5351,8 +3891,6 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5367,8 +3905,6 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", "engines": { @@ -5380,8 +3916,6 @@ }, "node_modules/dedent": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5395,15 +3929,11 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -5412,9 +3942,7 @@ }, "node_modules/deepmerge-ts": { "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" @@ -5422,8 +3950,6 @@ }, "node_modules/defaults": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", - "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", "dev": true, "license": "MIT", "engines": { @@ -5435,8 +3961,6 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, "license": "MIT", "engines": { @@ -5445,15 +3969,11 @@ }, "node_modules/defu": { "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/degenerator": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5467,8 +3987,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { @@ -5477,8 +3995,6 @@ }, "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" @@ -5486,15 +4002,11 @@ }, "node_modules/destr": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/detect-newline": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -5503,8 +4015,6 @@ }, "node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "license": "ISC", "dependencies": { @@ -5514,8 +4024,6 @@ }, "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, "license": "BSD-3-Clause", "engines": { @@ -5524,8 +4032,6 @@ }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -5537,8 +4043,6 @@ }, "node_modules/doctrine": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -5549,8 +4053,6 @@ }, "node_modules/dotenv": { "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5561,8 +4063,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5575,15 +4075,11 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "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" @@ -5591,15 +4087,11 @@ }, "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/effect": { "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -5608,15 +4100,11 @@ }, "node_modules/electron-to-chromium": { "version": "1.5.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "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": { @@ -5628,16 +4116,12 @@ }, "node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/empathic": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=14" @@ -5645,14 +4129,10 @@ }, "node_modules/enabled": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "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" @@ -5660,8 +4140,6 @@ }, "node_modules/encoding": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", "optional": true, @@ -5671,8 +4149,6 @@ }, "node_modules/enquirer": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "license": "MIT", "dependencies": { @@ -5684,8 +4160,6 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -5694,8 +4168,6 @@ }, "node_modules/envalid": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.0.tgz", - "integrity": "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -5706,8 +4178,6 @@ }, "node_modules/environment": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "engines": { @@ -5719,15 +4189,11 @@ }, "node_modules/err-code": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true, "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5736,8 +4202,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5745,8 +4209,6 @@ }, "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" @@ -5754,8 +4216,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5766,8 +4226,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -5782,8 +4240,6 @@ }, "node_modules/escalade": { "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": { @@ -5792,14 +4248,10 @@ }, "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/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -5811,8 +4263,6 @@ }, "node_modules/escodegen": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5833,8 +4283,6 @@ }, "node_modules/escodegen/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", "optional": true, @@ -5844,8 +4292,6 @@ }, "node_modules/eslint": { "version": "9.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", - "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", "dev": true, "license": "MIT", "dependencies": { @@ -5904,8 +4350,6 @@ }, "node_modules/eslint-config-prettier": { "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { @@ -5920,8 +4364,6 @@ }, "node_modules/eslint-plugin-prettier": { "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "license": "MIT", "dependencies": { @@ -5951,8 +4393,6 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5968,8 +4408,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5981,8 +4419,6 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5992,8 +4428,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6005,8 +4439,6 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -6015,8 +4447,6 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -6028,8 +4458,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6046,8 +4474,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6059,8 +4485,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "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": { @@ -6073,8 +4497,6 @@ }, "node_modules/esquery": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6086,8 +4508,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6099,8 +4519,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6109,8 +4527,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -6118,8 +4534,6 @@ }, "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" @@ -6127,22 +4541,16 @@ }, "node_modules/eventemitter2": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", - "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", "dev": true, "license": "MIT" }, "node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true, "license": "MIT" }, "node_modules/events-universal": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6151,8 +4559,6 @@ }, "node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -6175,8 +4581,6 @@ }, "node_modules/exit-x": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, "license": "MIT", "engines": { @@ -6185,8 +4589,6 @@ }, "node_modules/expect": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -6203,15 +4605,11 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, "node_modules/express": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -6252,8 +4650,6 @@ }, "node_modules/express/node_modules/content-disposition": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -6264,8 +4660,6 @@ }, "node_modules/express/node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -6273,15 +4667,11 @@ }, "node_modules/exsolve": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/ext-list": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "license": "MIT", "dependencies": { @@ -6293,8 +4683,6 @@ }, "node_modules/ext-name": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6307,8 +4695,6 @@ }, "node_modules/extrareqp2": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", - "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", "dev": true, "license": "MIT", "dependencies": { @@ -6317,9 +4703,7 @@ }, "node_modules/fast-check": { "version": "3.23.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", - "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -6340,9 +4724,7 @@ }, "node_modules/fast-check/node_modules/pure-rand": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -6357,29 +4739,21 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, "node_modules/fast-fifo": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -6395,8 +4769,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -6408,36 +4780,26 @@ }, "node_modules/fast-json-patch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "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, "license": "MIT" }, "node_modules/fastq": { "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -6446,8 +4808,6 @@ }, "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": { @@ -6456,15 +4816,11 @@ }, "node_modules/fclone": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", "dev": true, "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -6481,21 +4837,15 @@ }, "node_modules/fecha": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, "node_modules/fflate": { "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6507,8 +4857,6 @@ }, "node_modules/file-stream-rotator": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", - "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", "license": "MIT", "dependencies": { "moment": "^2.29.1" @@ -6516,8 +4864,6 @@ }, "node_modules/file-type": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", - "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", "dev": true, "license": "MIT", "dependencies": { @@ -6535,8 +4881,6 @@ }, "node_modules/filename-reserved-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", - "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, "license": "MIT", "engines": { @@ -6548,8 +4892,6 @@ }, "node_modules/filenamify": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", - "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6564,8 +4906,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -6577,8 +4917,6 @@ }, "node_modules/finalhandler": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -6594,8 +4932,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -6611,8 +4947,6 @@ }, "node_modules/find-versions": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", - "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -6627,8 +4961,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -6641,21 +4973,15 @@ }, "node_modules/flatted": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -6675,8 +5001,6 @@ }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -6692,8 +5016,6 @@ }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -6705,8 +5027,6 @@ }, "node_modules/form-data": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -6722,8 +5042,6 @@ }, "node_modules/form-data-encoder": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true, "license": "MIT", "engines": { @@ -6732,8 +5050,6 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "engines": { @@ -6742,8 +5058,6 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "dependencies": { @@ -6755,8 +5069,6 @@ }, "node_modules/formidable": { "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { @@ -6773,8 +5085,6 @@ }, "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" @@ -6782,8 +5092,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6791,8 +5099,6 @@ }, "node_modules/fs-minipass": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, "license": "ISC", "dependencies": { @@ -6804,8 +5110,6 @@ }, "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==", "license": "ISC" }, "node_modules/fsevents": { @@ -6825,8 +5129,6 @@ }, "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" @@ -6834,8 +5136,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "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": { @@ -6844,8 +5144,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -6854,8 +5152,6 @@ }, "node_modules/get-east-asian-width": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, "license": "MIT", "engines": { @@ -6867,8 +5163,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6891,8 +5185,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -6901,8 +5193,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6914,8 +5204,6 @@ }, "node_modules/get-stream": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -6927,8 +5215,6 @@ }, "node_modules/get-tsconfig": { "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6940,8 +5226,6 @@ }, "node_modules/get-uri": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -6955,9 +5239,7 @@ }, "node_modules/giget": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -6973,22 +5255,16 @@ }, "node_modules/git-node-fs": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", "dev": true, "license": "MIT" }, "node_modules/git-sha1": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", "dev": true, "license": "MIT" }, "node_modules/glob": { "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -7008,8 +5284,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -7021,8 +5295,6 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -7034,8 +5306,6 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", "dependencies": { @@ -7055,8 +5325,6 @@ }, "node_modules/globby/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -7065,8 +5333,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7077,8 +5343,6 @@ }, "node_modules/got": { "version": "13.0.0", - "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", - "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, "license": "MIT", "dependencies": { @@ -7103,22 +5367,16 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7139,8 +5397,6 @@ }, "node_modules/handlebars/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": { @@ -7149,8 +5405,6 @@ }, "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, "license": "MIT", "engines": { @@ -7159,8 +5413,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7171,8 +5423,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -7187,8 +5437,6 @@ }, "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" @@ -7199,8 +5447,6 @@ }, "node_modules/helmet": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -7208,8 +5454,6 @@ }, "node_modules/hpp": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", - "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", "license": "ISC", "dependencies": { "lodash": "^4.17.12", @@ -7221,8 +5465,6 @@ }, "node_modules/hpp/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" @@ -7230,8 +5472,6 @@ }, "node_modules/hpp/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==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7239,8 +5479,6 @@ }, "node_modules/hpp/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==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -7251,8 +5489,6 @@ }, "node_modules/hpp/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", @@ -7264,22 +5500,16 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-cache-semantics": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, "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", @@ -7294,8 +5524,6 @@ }, "node_modules/http-errors/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" @@ -7303,8 +5531,6 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -7317,8 +5543,6 @@ }, "node_modules/http2-wrapper": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7331,8 +5555,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -7345,8 +5567,6 @@ }, "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": { @@ -7355,8 +5575,6 @@ }, "node_modules/husky": { "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -7371,8 +5589,6 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7383,8 +5599,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==", "dev": true, "funding": [ { @@ -7404,8 +5618,6 @@ }, "node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -7414,15 +5626,11 @@ }, "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, "license": "ISC" }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7438,8 +5646,6 @@ }, "node_modules/import-local": { "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": { @@ -7458,8 +5664,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -7468,9 +5672,6 @@ }, "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.", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -7479,21 +5680,15 @@ }, "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/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, "node_modules/inspect-with-kind": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", - "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", "dev": true, "license": "ISC", "dependencies": { @@ -7502,8 +5697,6 @@ }, "node_modules/ip-address": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "dev": true, "license": "MIT", "engines": { @@ -7512,8 +5705,6 @@ }, "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" @@ -7521,8 +5712,6 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, @@ -7541,8 +5730,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -7557,8 +5744,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -7567,8 +5752,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7583,8 +5766,6 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", - "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": { @@ -7593,8 +5774,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -7606,8 +5785,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -7616,8 +5793,6 @@ }, "node_modules/is-plain-obj": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "license": "MIT", "engines": { @@ -7626,14 +5801,10 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -7644,15 +5815,11 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "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": { @@ -7661,8 +5828,6 @@ }, "node_modules/istanbul-lib-instrument": { "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": { @@ -7678,8 +5843,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "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": { @@ -7693,8 +5856,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7708,8 +5869,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7722,8 +5881,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -7738,8 +5895,6 @@ }, "node_modules/jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { @@ -7765,8 +5920,6 @@ }, "node_modules/jest-changed-files": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7780,8 +5933,6 @@ }, "node_modules/jest-circus": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { @@ -7812,8 +5963,6 @@ }, "node_modules/jest-cli": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { @@ -7845,8 +5994,6 @@ }, "node_modules/jest-config": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { @@ -7897,8 +6044,6 @@ }, "node_modules/jest-diff": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { @@ -7913,8 +6058,6 @@ }, "node_modules/jest-docblock": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -7926,8 +6069,6 @@ }, "node_modules/jest-each": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7943,8 +6084,6 @@ }, "node_modules/jest-environment-node": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { @@ -7962,8 +6101,6 @@ }, "node_modules/jest-haste-map": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -7987,8 +6124,6 @@ }, "node_modules/jest-leak-detector": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8001,8 +6136,6 @@ }, "node_modules/jest-matcher-utils": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -8017,8 +6150,6 @@ }, "node_modules/jest-message-util": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { @@ -8038,8 +6169,6 @@ }, "node_modules/jest-mock": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { @@ -8053,8 +6182,6 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { @@ -8071,8 +6198,6 @@ }, "node_modules/jest-regex-util": { "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { @@ -8081,8 +6206,6 @@ }, "node_modules/jest-resolve": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { @@ -8101,8 +6224,6 @@ }, "node_modules/jest-resolve-dependencies": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { @@ -8115,8 +6236,6 @@ }, "node_modules/jest-runner": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8149,8 +6268,6 @@ }, "node_modules/jest-runtime": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { @@ -8183,8 +6300,6 @@ }, "node_modules/jest-snapshot": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { @@ -8216,8 +6331,6 @@ }, "node_modules/jest-util": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { @@ -8232,23 +6345,8 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/jest-validate": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -8265,8 +6363,6 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -8278,8 +6374,6 @@ }, "node_modules/jest-watcher": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { @@ -8298,8 +6392,6 @@ }, "node_modules/jest-worker": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { @@ -8315,8 +6407,6 @@ }, "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": { @@ -8331,9 +6421,7 @@ }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8341,8 +6429,6 @@ }, "node_modules/js-git": { "version": "0.7.8", - "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", - "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", "dev": true, "license": "MIT", "dependencies": { @@ -8354,15 +6440,11 @@ }, "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, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8373,8 +6455,6 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -8386,44 +6466,32 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC", "optional": true }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -8435,8 +6503,6 @@ }, "node_modules/jsonwebtoken": { "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", @@ -8457,8 +6523,6 @@ }, "node_modules/jwa": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -8468,8 +6532,6 @@ }, "node_modules/jws": { "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", @@ -8478,8 +6540,6 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -8488,8 +6548,6 @@ }, "node_modules/kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { @@ -8498,14 +6556,10 @@ }, "node_modules/kuler": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", - "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": { @@ -8514,8 +6568,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8528,21 +6580,15 @@ }, "node_modules/libphonenumber-js": { "version": "1.12.25", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.25.tgz", - "integrity": "sha512-u90tUu/SEF8b+RaDKCoW7ZNFDakyBtFlX1ex3J+VH+ElWes/UaitJLt/w4jGu8uAE41lltV/s+kMVtywcMEg7g==", "license": "MIT" }, "node_modules/lines-and-columns": { "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, "license": "MIT" }, "node_modules/lint-staged": { "version": "16.2.6", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", - "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, "license": "MIT", "dependencies": { @@ -8566,8 +6612,6 @@ }, "node_modules/lint-staged/node_modules/commander": { "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "dev": true, "license": "MIT", "engines": { @@ -8576,8 +6620,6 @@ }, "node_modules/listr2": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { @@ -8594,8 +6636,6 @@ }, "node_modules/listr2/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -8607,15 +6647,11 @@ }, "node_modules/listr2/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8627,13 +6663,11 @@ "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/listr2/node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -8650,8 +6684,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -8666,90 +6698,60 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, "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==", "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==", "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "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==", "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==", "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==", "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==", "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "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==", "license": "MIT" }, "node_modules/log-update": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "license": "MIT", "dependencies": { @@ -8768,8 +6770,6 @@ }, "node_modules/log-update/node_modules/ansi-escapes": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", - "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8784,8 +6784,6 @@ }, "node_modules/log-update/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -8797,15 +6795,11 @@ }, "node_modules/log-update/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8822,8 +6816,6 @@ }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -8840,8 +6832,6 @@ }, "node_modules/logform": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -8857,8 +6847,6 @@ }, "node_modules/lowercase-keys": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "license": "MIT", "engines": { @@ -8870,8 +6858,6 @@ }, "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, "license": "ISC", "dependencies": { @@ -8880,8 +6866,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -8896,15 +6880,11 @@ }, "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, "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { @@ -8926,8 +6906,6 @@ }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -8936,8 +6914,6 @@ }, "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": { @@ -8946,8 +6922,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8955,8 +6929,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8964,8 +6936,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -8976,15 +6946,11 @@ }, "node_modules/merge-stream": { "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, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -8993,8 +6959,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", "engines": { @@ -9003,8 +6967,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -9015,10 +6977,21 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "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": { @@ -9030,8 +7003,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9039,8 +7010,6 @@ }, "node_modules/mime-types": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -9051,8 +7020,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -9061,8 +7028,6 @@ }, "node_modules/mimic-function": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { @@ -9074,8 +7039,6 @@ }, "node_modules/mimic-response": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "license": "MIT", "engines": { @@ -9087,8 +7050,6 @@ }, "node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -9103,8 +7064,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { @@ -9113,8 +7072,6 @@ }, "node_modules/minipass": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -9123,8 +7080,6 @@ }, "node_modules/minipass-collect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "license": "ISC", "dependencies": { @@ -9136,8 +7091,6 @@ }, "node_modules/minipass-fetch": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9154,8 +7107,6 @@ }, "node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "license": "ISC", "dependencies": { @@ -9167,8 +7118,6 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9180,15 +7129,11 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { @@ -9200,8 +7145,6 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9213,15 +7156,11 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { @@ -9233,8 +7172,6 @@ }, "node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9246,15 +7183,11 @@ }, "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minizlib": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -9266,8 +7199,6 @@ }, "node_modules/mkdirp": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", "bin": { @@ -9279,15 +7210,11 @@ }, "node_modules/module-details-from-path": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "dev": true, "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { "node": "*" @@ -9295,8 +7222,6 @@ }, "node_modules/morgan": { "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", @@ -9311,8 +7236,6 @@ }, "node_modules/morgan/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" @@ -9320,14 +7243,10 @@ }, "node_modules/morgan/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/morgan/node_modules/on-finished": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -9338,21 +7257,15 @@ }, "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==", "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true, "license": "ISC" }, "node_modules/mylas": { "version": "2.1.13", - "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", - "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", "dev": true, "license": "MIT", "engines": { @@ -9365,8 +7278,6 @@ }, "node_modules/nano-spawn": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, "license": "MIT", "engines": { @@ -9378,8 +7289,6 @@ }, "node_modules/napi-postinstall": { "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -9394,15 +7303,11 @@ }, "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, "license": "MIT" }, "node_modules/needle": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "dev": true, "license": "MIT", "dependencies": { @@ -9419,8 +7324,6 @@ }, "node_modules/needle/node_modules/debug": { "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9429,8 +7332,6 @@ }, "node_modules/needle/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==", "dev": true, "license": "MIT", "dependencies": { @@ -9442,8 +7343,6 @@ }, "node_modules/negotiator": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9451,15 +7350,11 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/netmask": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, "license": "MIT", "engines": { @@ -9468,8 +7363,6 @@ }, "node_modules/node-addon-api": { "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -9477,8 +7370,6 @@ }, "node_modules/node-config": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/node-config/-/node-config-0.0.2.tgz", - "integrity": "sha512-NZu10oQ7jN6eDkRK22YX8j87mS02CuarKqoWIPcU6MKbuQ5dfLkvjOsWyN4ov+hPkIR7BppEueUg3QtcsRO7MA==", "dev": true, "engines": { "node": ">=0.1.99" @@ -9486,15 +7377,11 @@ }, "node_modules/node-fetch-native": { "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-gyp": { "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9518,8 +7405,6 @@ }, "node_modules/node-gyp-build": { "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -9529,8 +7414,6 @@ }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "license": "ISC", "engines": { @@ -9539,8 +7422,6 @@ }, "node_modules/node-gyp/node_modules/which": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9555,22 +7436,16 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nodemon": { "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "dev": true, "license": "MIT", "dependencies": { @@ -9598,8 +7473,6 @@ }, "node_modules/nodemon/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -9607,10 +7480,46 @@ "concat-map": "0.0.1" } }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" + } + }, "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, "license": "MIT", "engines": { @@ -9619,8 +7528,6 @@ }, "node_modules/nodemon/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -9630,10 +7537,34 @@ "node": "*" } }, + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "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": { @@ -9645,8 +7576,6 @@ }, "node_modules/nopt": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { @@ -9661,8 +7590,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { @@ -9671,8 +7598,6 @@ }, "node_modules/normalize-url": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", - "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, "license": "MIT", "engines": { @@ -9684,8 +7609,6 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -9697,9 +7620,7 @@ }, "node_modules/nypm": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -9717,8 +7638,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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9726,8 +7645,6 @@ }, "node_modules/object-hash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -9735,8 +7652,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9747,15 +7662,11 @@ }, "node_modules/ohash": { "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "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" @@ -9766,8 +7677,6 @@ }, "node_modules/on-headers": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9775,8 +7684,6 @@ }, "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==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -9784,8 +7691,6 @@ }, "node_modules/one-time": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { "fn.name": "1.x.x" @@ -9793,8 +7698,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -9811,12 +7714,11 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", "peer": true }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -9833,8 +7735,6 @@ }, "node_modules/p-cancelable": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "license": "MIT", "engines": { @@ -9843,8 +7743,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -9859,8 +7757,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -9875,8 +7771,6 @@ }, "node_modules/p-map": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", "engines": { @@ -9888,8 +7782,6 @@ }, "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": { @@ -9898,8 +7790,6 @@ }, "node_modules/pac-proxy-agent": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, "license": "MIT", "dependencies": { @@ -9918,8 +7808,6 @@ }, "node_modules/pac-resolver": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "license": "MIT", "dependencies": { @@ -9932,22 +7820,16 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "dev": true, "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -9959,8 +7841,6 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -9978,8 +7858,6 @@ }, "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" @@ -9987,8 +7865,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -9997,8 +7873,6 @@ }, "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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10006,8 +7880,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -10016,15 +7888,11 @@ }, "node_modules/path-parse": { "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, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10040,15 +7908,11 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -10057,8 +7921,6 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -10067,40 +7929,32 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/picocolors": { "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -10108,8 +7962,6 @@ }, "node_modules/pidtree": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, "license": "MIT", "bin": { @@ -10121,8 +7973,6 @@ }, "node_modules/pidusage": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", - "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", "dev": true, "license": "MIT", "dependencies": { @@ -10134,8 +7984,6 @@ }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -10144,8 +7992,6 @@ }, "node_modules/piscina": { "version": "4.9.2", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", - "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -10154,8 +8000,6 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", - "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": { @@ -10167,8 +8011,6 @@ }, "node_modules/pkg-dir/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": { @@ -10181,8 +8023,6 @@ }, "node_modules/pkg-dir/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": { @@ -10194,8 +8034,6 @@ }, "node_modules/pkg-dir/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": { @@ -10210,8 +8048,6 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", - "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": { @@ -10223,9 +8059,7 @@ }, "node_modules/pkg-types": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -10235,8 +8069,6 @@ }, "node_modules/plimit-lit": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", - "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", "dev": true, "license": "MIT", "dependencies": { @@ -10248,8 +8080,6 @@ }, "node_modules/pm2": { "version": "6.0.13", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.13.tgz", - "integrity": "sha512-1hS/adMgKoDpX4S1ichJW8SiGpex+oBSZK31dP1FSYOOGtaeuemXzhXPOCefmddgIY4K6v7uu+7xNPnmEnK3ag==", "dev": true, "license": "AGPL-3.0", "dependencies": { @@ -10298,8 +8128,6 @@ }, "node_modules/pm2-axon": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", - "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", "dev": true, "license": "MIT", "dependencies": { @@ -10314,8 +8142,6 @@ }, "node_modules/pm2-axon-rpc": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", - "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", "dev": true, "license": "MIT", "dependencies": { @@ -10327,8 +8153,6 @@ }, "node_modules/pm2-deploy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", - "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", "dev": true, "license": "MIT", "dependencies": { @@ -10341,8 +8165,6 @@ }, "node_modules/pm2-multimeter": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", - "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", "dev": true, "license": "MIT/X11", "dependencies": { @@ -10351,8 +8173,6 @@ }, "node_modules/pm2-sysmonit": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", - "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", "dev": true, "license": "Apache", "optional": true, @@ -10366,8 +8186,6 @@ }, "node_modules/pm2-sysmonit/node_modules/pidusage": { "version": "2.0.21", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", - "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", "dev": true, "license": "MIT", "optional": true, @@ -10378,17 +8196,77 @@ "node": ">=8" } }, + "node_modules/pm2/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/pm2/node_modules/commander": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true, "license": "MIT" }, + "node_modules/pm2/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pm2/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pm2/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/pm2/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -10400,8 +8278,6 @@ }, "node_modules/pm2/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": { @@ -10410,8 +8286,6 @@ }, "node_modules/pm2/node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -10421,8 +8295,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -10431,8 +8303,6 @@ }, "node_modules/prettier": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -10447,8 +8317,6 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "license": "MIT", "dependencies": { @@ -10460,8 +8328,6 @@ }, "node_modules/pretty-format": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { @@ -10475,8 +8341,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -10488,9 +8352,7 @@ }, "node_modules/prisma": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", - "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -10514,8 +8376,6 @@ }, "node_modules/proc-log": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { @@ -10524,8 +8384,6 @@ }, "node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "license": "MIT", "dependencies": { @@ -10538,8 +8396,6 @@ }, "node_modules/promptly": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", - "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", "dev": true, "license": "MIT", "dependencies": { @@ -10548,8 +8404,6 @@ }, "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", @@ -10561,8 +8415,6 @@ }, "node_modules/proxy-agent": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10581,8 +8433,6 @@ }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -10591,22 +8441,16 @@ }, "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==", "dev": true, "license": "MIT" }, "node_modules/pstree.remy": { "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, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -10615,8 +8459,6 @@ }, "node_modules/pure-rand": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -10632,8 +8474,6 @@ }, "node_modules/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -10647,8 +8487,6 @@ }, "node_modules/queue-lit": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", - "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", "dev": true, "license": "MIT", "engines": { @@ -10657,8 +8495,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -10678,8 +8514,6 @@ }, "node_modules/quick-lru": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "license": "MIT", "engines": { @@ -10691,8 +8525,6 @@ }, "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" @@ -10700,8 +8532,6 @@ }, "node_modules/raw-body": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -10715,8 +8545,6 @@ }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -10731,9 +8559,7 @@ }, "node_modules/rc9": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -10742,15 +8568,11 @@ }, "node_modules/react-is": { "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/read": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "license": "ISC", "dependencies": { @@ -10762,8 +8584,6 @@ }, "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", @@ -10775,28 +8595,25 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect-metadata": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -10805,8 +8622,6 @@ }, "node_modules/require-in-the-middle": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", - "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", "dev": true, "license": "MIT", "dependencies": { @@ -10820,8 +8635,6 @@ }, "node_modules/resolve": { "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10841,15 +8654,11 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true, "license": "MIT" }, "node_modules/resolve-cwd": { "version": "3.0.0", - "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": { @@ -10861,8 +8670,6 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -10871,8 +8678,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -10881,8 +8686,6 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -10891,8 +8694,6 @@ }, "node_modules/responselike": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "license": "MIT", "dependencies": { @@ -10907,8 +8708,6 @@ }, "node_modules/restore-cursor": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { @@ -10924,8 +8723,6 @@ }, "node_modules/restore-cursor/node_modules/onetime": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10940,8 +8737,6 @@ }, "node_modules/restore-cursor/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -10953,8 +8748,6 @@ }, "node_modules/retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { @@ -10963,8 +8756,6 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -10974,15 +8765,11 @@ }, "node_modules/rfdc": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -10997,8 +8784,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -11021,8 +8806,6 @@ }, "node_modules/run-series": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", "dev": true, "funding": [ { @@ -11042,8 +8825,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", @@ -11062,8 +8843,6 @@ }, "node_modules/safe-stable-stringify": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -11071,21 +8850,15 @@ }, "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/sax": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, "license": "ISC" }, "node_modules/seek-bzip": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", - "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", "dev": true, "license": "MIT", "dependencies": { @@ -11098,8 +8871,6 @@ }, "node_modules/seek-bzip/node_modules/commander": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "license": "MIT", "engines": { @@ -11108,8 +8879,6 @@ }, "node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11120,8 +8889,6 @@ }, "node_modules/semver-regex": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", "engines": { @@ -11133,8 +8900,6 @@ }, "node_modules/semver-truncate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", - "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -11149,8 +8914,6 @@ }, "node_modules/send": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -11171,8 +8934,6 @@ }, "node_modules/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -11186,14 +8947,10 @@ }, "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/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==", "dev": true, "license": "MIT", "dependencies": { @@ -11205,8 +8962,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -11215,15 +8970,11 @@ }, "node_modules/shimmer": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -11241,8 +8992,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -11257,8 +9006,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11275,8 +9022,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11294,15 +9039,11 @@ }, "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==", "dev": true, "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": { @@ -11314,8 +9055,6 @@ }, "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": { @@ -11324,8 +9063,6 @@ }, "node_modules/slice-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { @@ -11341,8 +9078,6 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -11354,8 +9089,6 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { @@ -11365,8 +9098,6 @@ }, "node_modules/socks": { "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { @@ -11380,8 +9111,6 @@ }, "node_modules/socks-proxy-agent": { "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { @@ -11395,8 +9124,6 @@ }, "node_modules/sort-keys": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, "license": "MIT", "dependencies": { @@ -11408,8 +9135,6 @@ }, "node_modules/sort-keys-length": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, "license": "MIT", "dependencies": { @@ -11421,8 +9146,6 @@ }, "node_modules/source-map": { "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -11431,8 +9154,6 @@ }, "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": { @@ -11442,8 +9163,6 @@ }, "node_modules/source-map-support/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": { @@ -11452,15 +9171,11 @@ }, "node_modules/sprintf-js": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { @@ -11472,8 +9187,6 @@ }, "node_modules/stack-trace": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", "engines": { "node": "*" @@ -11481,8 +9194,6 @@ }, "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": { @@ -11494,8 +9205,6 @@ }, "node_modules/stack-utils/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": { @@ -11504,8 +9213,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -11513,8 +9220,6 @@ }, "node_modules/streamx": { "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { @@ -11525,8 +9230,6 @@ }, "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" @@ -11534,8 +9237,6 @@ }, "node_modules/string-argv": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", "engines": { @@ -11544,8 +9245,6 @@ }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11558,8 +9257,6 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "engines": { @@ -11568,8 +9265,6 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "dependencies": { @@ -11581,8 +9276,6 @@ }, "node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -11600,8 +9293,6 @@ "node_modules/string-width-cjs": { "name": "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==", "dev": true, "license": "MIT", "dependencies": { @@ -11615,8 +9306,6 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -11625,15 +9314,11 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -11642,8 +9327,6 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -11655,8 +9338,6 @@ }, "node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -11672,8 +9353,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -11685,8 +9364,6 @@ }, "node_modules/strip-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -11695,8 +9372,6 @@ }, "node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -11705,8 +9380,6 @@ }, "node_modules/strip-dirs": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", - "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, "license": "ISC", "dependencies": { @@ -11716,8 +9389,6 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -11726,8 +9397,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -11739,8 +9408,6 @@ }, "node_modules/strtok3": { "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -11756,8 +9423,6 @@ }, "node_modules/superagent": { "version": "10.2.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", - "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "dev": true, "license": "MIT", "dependencies": { @@ -11777,8 +9442,6 @@ }, "node_modules/supertest": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", - "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "license": "MIT", "dependencies": { @@ -11791,8 +9454,6 @@ }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -11804,8 +9465,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "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": { @@ -11817,8 +9476,6 @@ }, "node_modules/swagger-jsdoc": { "version": "6.2.8", - "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", - "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", "license": "MIT", "dependencies": { "commander": "6.2.0", @@ -11837,8 +9494,6 @@ }, "node_modules/swagger-jsdoc/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -11847,8 +9502,6 @@ }, "node_modules/swagger-jsdoc/node_modules/commander": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", "license": "MIT", "engines": { "node": ">= 6" @@ -11856,9 +9509,6 @@ }, "node_modules/swagger-jsdoc/node_modules/glob": { "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -11877,8 +9527,6 @@ }, "node_modules/swagger-jsdoc/node_modules/minimatch": { "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" @@ -11889,8 +9537,6 @@ }, "node_modules/swagger-jsdoc/node_modules/yaml": { "version": "2.0.0-1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", - "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "license": "ISC", "engines": { "node": ">= 6" @@ -11898,8 +9544,6 @@ }, "node_modules/swagger-parser": { "version": "10.0.3", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "10.0.3" @@ -11910,8 +9554,6 @@ }, "node_modules/swagger-ui-dist": { "version": "5.30.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.1.tgz", - "integrity": "sha512-4mNAUM31sr52K3JcK9qiGbfsFKNh/dm3PkEe+F9FAM31YY/NoRYUgsR/L6d7LLFn6PgZXtBG2ygp8+7UnpUIPg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -11919,8 +9561,6 @@ }, "node_modules/swagger-ui-express": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", - "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", "license": "MIT", "dependencies": { "swagger-ui-dist": ">=5.0.0" @@ -11934,8 +9574,6 @@ }, "node_modules/synckit": { "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { @@ -11950,8 +9588,6 @@ }, "node_modules/systeminformation": { "version": "5.27.11", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.11.tgz", - "integrity": "sha512-K3Lto/2m3K2twmKHdgx5B+0in9qhXK4YnoT9rIlgwN/4v7OV5c8IjbeAUkuky/6VzCQC7iKCAqi8rZathCdjHg==", "dev": true, "license": "MIT", "optional": true, @@ -11978,8 +9614,6 @@ }, "node_modules/tar": { "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11995,8 +9629,6 @@ }, "node_modules/tar-stream": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12007,8 +9639,6 @@ }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -12017,8 +9647,6 @@ }, "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": { @@ -12032,8 +9660,6 @@ }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -12043,9 +9669,6 @@ }, "node_modules/test-exclude/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", "dev": true, "license": "ISC", "dependencies": { @@ -12065,8 +9688,6 @@ }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -12078,8 +9699,6 @@ }, "node_modules/text-decoder": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12088,65 +9707,22 @@ }, "node_modules/text-hex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, "node_modules/through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "devOptional": true - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -12158,46 +9734,13 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -12209,8 +9752,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" @@ -12218,8 +9759,6 @@ }, "node_modules/token-types": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12237,8 +9776,6 @@ }, "node_modules/touch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, "license": "ISC", "bin": { @@ -12247,8 +9784,6 @@ }, "node_modules/triple-beam": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", "engines": { "node": ">= 14.0.0" @@ -12256,8 +9791,6 @@ }, "node_modules/ts-api-utils": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { @@ -12269,8 +9802,6 @@ }, "node_modules/ts-jest": { "version": "29.4.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", - "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12322,8 +9853,6 @@ }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -12335,8 +9864,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, "license": "MIT", "dependencies": { @@ -12379,8 +9906,6 @@ }, "node_modules/tsc-alias": { "version": "1.8.16", - "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", - "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", "dev": true, "license": "MIT", "dependencies": { @@ -12404,6 +9929,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -12425,8 +9951,6 @@ }, "node_modules/tsc-alias/node_modules/commander": { "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", "engines": { @@ -12438,6 +9962,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" }, @@ -12445,11 +9970,25 @@ "node": ">= 6" } }, + "node_modules/tsc-alias/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tsc-alias/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -12459,8 +9998,6 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "license": "MIT", "dependencies": { @@ -12474,8 +10011,6 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -12484,14 +10019,10 @@ }, "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==", "license": "0BSD" }, "node_modules/tv4": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", "dev": true, "license": [ { @@ -12509,8 +10040,6 @@ }, "node_modules/tx2": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", - "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", "dev": true, "license": "MIT", "optional": true, @@ -12520,8 +10049,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -12533,8 +10060,6 @@ }, "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": { @@ -12543,8 +10068,6 @@ }, "node_modules/type-fest": { "version": "0.21.3", - "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": { @@ -12556,8 +10079,6 @@ }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -12570,15 +10091,11 @@ }, "node_modules/typedi": { "version": "0.10.0", - "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", - "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12590,8 +10107,6 @@ }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -12604,8 +10119,6 @@ }, "node_modules/uint8array-extras": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "dev": true, "license": "MIT", "engines": { @@ -12617,8 +10130,6 @@ }, "node_modules/unbzip2-stream": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "license": "MIT", "dependencies": { @@ -12628,22 +10139,16 @@ }, "node_modules/undefsafe": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, "node_modules/unique-filename": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { @@ -12655,8 +10160,6 @@ }, "node_modules/unique-slug": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { @@ -12668,8 +10171,6 @@ }, "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" @@ -12677,8 +10178,6 @@ }, "node_modules/unrs-resolver": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -12712,8 +10211,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -12743,8 +10240,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -12753,21 +10248,15 @@ }, "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/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, "license": "MIT" }, "node_modules/v8-to-istanbul": { "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": { @@ -12781,8 +10270,6 @@ }, "node_modules/validator": { "version": "13.15.20", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", - "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -12790,8 +10277,6 @@ }, "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==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12799,8 +10284,6 @@ }, "node_modules/vizion": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", - "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12815,8 +10298,6 @@ }, "node_modules/vizion/node_modules/async": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "license": "MIT", "dependencies": { @@ -12825,8 +10306,6 @@ }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12835,8 +10314,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -12851,8 +10328,6 @@ }, "node_modules/winston": { "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -12873,8 +10348,6 @@ }, "node_modules/winston-daily-rotate-file": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", - "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", "license": "MIT", "dependencies": { "file-stream-rotator": "^0.6.1", @@ -12891,8 +10364,6 @@ }, "node_modules/winston-transport": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { "logform": "^2.7.0", @@ -12905,8 +10376,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -12915,15 +10384,11 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12941,8 +10406,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12959,8 +10422,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -12969,15 +10430,11 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -12986,8 +10443,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13001,8 +10456,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13014,8 +10467,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -13027,14 +10478,10 @@ }, "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==", "license": "ISC" }, "node_modules/write-file-atomic": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -13047,8 +10494,6 @@ }, "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -13060,8 +10505,6 @@ }, "node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "license": "MIT", "engines": { @@ -13082,8 +10525,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -13092,15 +10533,11 @@ }, "node_modules/yallist": { "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.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { @@ -13112,8 +10549,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -13131,8 +10566,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "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": { @@ -13141,8 +10574,6 @@ }, "node_modules/yargs/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==", "dev": true, "license": "MIT", "engines": { @@ -13151,15 +10582,11 @@ }, "node_modules/yargs/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==", "dev": true, "license": "MIT" }, "node_modules/yargs/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==", "dev": true, "license": "MIT", "engines": { @@ -13168,8 +10595,6 @@ }, "node_modules/yargs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13183,8 +10608,6 @@ }, "node_modules/yargs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13196,8 +10619,6 @@ }, "node_modules/yauzl": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", - "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", "dev": true, "license": "MIT", "dependencies": { @@ -13210,8 +10631,6 @@ }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", "engines": { @@ -13220,8 +10639,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -13233,8 +10650,6 @@ }, "node_modules/z-schema": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", - "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", "license": "MIT", "dependencies": { "lodash.get": "^4.4.2", @@ -13253,8 +10668,6 @@ }, "node_modules/z-schema/node_modules/commander": { "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "optional": true, "engines": { From 74976b83d4545d5c7d9e76242b4b9fbaec0aeb78 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 2 Nov 2025 23:07:31 +0200 Subject: [PATCH 013/210] Added sign up with google logic --- package-lock.json | 195 +++++++++++++++++++++++ package.json | 5 + src/app.ts | 5 + src/config/index.ts | 3 +- src/controllers/googleAuth.controller.ts | 40 +++++ src/dtos/googleUsers.dto.ts | 4 + src/dtos/users.dto.ts | 4 + src/middlewares/auth.middleware.ts | 3 +- src/routes/auth.route.ts | 6 +- src/services/googleAuth.service.ts | 25 +++ src/utils/passsportGoogle.ts | 50 ++++++ 11 files changed, 337 insertions(+), 3 deletions(-) create mode 100644 src/controllers/googleAuth.controller.ts create mode 100644 src/dtos/googleUsers.dto.ts create mode 100644 src/services/googleAuth.service.ts create mode 100644 src/utils/passsportGoogle.ts diff --git a/package-lock.json b/package-lock.json index 56885c8..d03e6ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,13 @@ "dotenv": "^17.2.3", "envalid": "^8.1.0", "express": "^5.1.0", + "express-session": "^1.18.2", "helmet": "^8.1.0", "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", @@ -38,11 +41,13 @@ "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.5", + "@types/express-session": "^1.18.2", "@types/hpp": "^0.2.7", "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", "@types/node": "^24.9.2", + "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", @@ -2056,6 +2061,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/hpp": { "version": "0.2.7", "dev": true, @@ -2148,6 +2162,46 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/oauth": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.17.tgz", + "integrity": "sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "node_modules/@types/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "dev": true, @@ -2983,6 +3037,14 @@ ], "license": "MIT" }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.8.22", "dev": true, @@ -4648,6 +4710,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/content-disposition": { "version": "1.0.0", "license": "MIT", @@ -7636,6 +7734,11 @@ "node": "^14.16.0 || >=16.10.0" } }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==" + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -7863,6 +7966,61 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -7932,6 +8090,11 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pend": { "version": "1.2.0", "dev": true, @@ -8523,6 +8686,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "license": "MIT", @@ -10117,6 +10288,22 @@ "node": ">=0.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/uint8array-extras": { "version": "1.5.0", "dev": true, @@ -10250,6 +10437,14 @@ "version": "1.0.2", "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==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "dev": true, diff --git a/package.json b/package.json index 6f8b2da..a6f76af 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,13 @@ "dotenv": "^17.2.3", "envalid": "^8.1.0", "express": "^5.1.0", + "express-session": "^1.18.2", "helmet": "^8.1.0", "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", @@ -51,11 +54,13 @@ "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.5", + "@types/express-session": "^1.18.2", "@types/hpp": "^0.2.7", "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", "@types/node": "^24.9.2", + "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", diff --git a/src/app.ts b/src/app.ts index d5c6fc3..22d4aa8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,6 +12,9 @@ import { NODE_ENV, PORT, LOG_FORMAT, ORIGIN, CREDENTIALS } from '@config'; import { Routes } from '@interfaces/routes.interface'; import { ErrorMiddleware } from '@middlewares/error.middleware'; import { logger, stream } from '@utils/logger'; +// Google OAuth Imports +import passport from 'passport'; +import '@utils/passsportGoogle'; export class App { public app: express.Application; @@ -51,8 +54,10 @@ export class App { this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); this.app.use(cookieParser()); + this.app.use(passport.initialize()); } + private initializeRoutes(routes: Routes[]) { routes.forEach(route => { this.app.use('/', route.router); diff --git a/src/config/index.ts b/src/config/index.ts index 548ccf4..525b0b4 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,6 +2,7 @@ import { config } from 'dotenv'; config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); export const CREDENTIALS = process.env.CREDENTIALS === 'true'; -export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET } = process.env; +export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET, + GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL } = process.env; export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts new file mode 100644 index 0000000..8a502c8 --- /dev/null +++ b/src/controllers/googleAuth.controller.ts @@ -0,0 +1,40 @@ +import { AuthService } from "@/services/auth.service"; +import passport from "passport"; +import { Container } from "typedi"; +import { NextFunction, Request, Response } from "express"; +import { User } from "@/interfaces/users.interface"; + +export class GoogleAuthController { + public authService = Container.get(AuthService); + + public googleOAuth = passport.authenticate('google', { + scope: ['profile', 'email'], + }); + + public googleOAuthCallback = (req: Request, res: Response, next: NextFunction) => { + passport.authenticate('google', { + failureRedirect: '/login', + }, async (err, user: User, info) => { + if (err) { + return next(err); + } + if (!user) { + return res.redirect('/login'); + } + + try { + // Generate JWT tokens for Google OAuth user + const tokenResponse = await this.authService.createTokens(user, true); + const cookies = this.authService.createCookies(tokenResponse); + + // Set JWT cookies + res.setHeader('Set-Cookie', cookies); + + // Redirect to dashboard with success + res.redirect('/dashboard'); + } catch (error) { + next(error); + } + })(req, res, next); + }; +} \ No newline at end of file diff --git a/src/dtos/googleUsers.dto.ts b/src/dtos/googleUsers.dto.ts new file mode 100644 index 0000000..ca7af30 --- /dev/null +++ b/src/dtos/googleUsers.dto.ts @@ -0,0 +1,4 @@ +export class CreateGoogleUsersDto { + public email: string; + public name: string; +} \ No newline at end of file diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index aa0a561..e4752fb 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -50,3 +50,7 @@ export class UpdateUserDto { @MaxLength(32) public password: string; } + +export class CompleteUserProfileDto { + +} diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 3aa6ab1..ec89d21 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -18,6 +18,7 @@ const getAuthorization = (req: Request) => { export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: NextFunction) => { try { + // Check for JWT token const Authorization = getAuthorization(req); if (Authorization) { const { id } = (await verify(Authorization, SECRET_KEY)) as DataStoredInToken; @@ -31,7 +32,7 @@ export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: next(new HttpException(401, 'Wrong authentication token')); } } else { - next(new HttpException(404, 'Authentication token missing')); + next(new HttpException(401, 'Authentication required')); } } catch (error) { next(new HttpException(401, 'Wrong authentication token')); diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index e6604b8..a1af644 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -3,12 +3,14 @@ import { AuthController } from '@controllers/auth.controller'; import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; +import { GoogleAuthController } from '@/controllers/googleAuth.controller'; import { ValidationMiddleware } from '@middlewares/validation.middleware'; export class AuthRoute implements Routes { public path = '/auth'; public router = Router(); public auth = new AuthController(); + public googleAuth = new GoogleAuthController(); constructor() { this.initializeRoutes(); @@ -18,6 +20,8 @@ export class AuthRoute implements Routes { this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); - this.router.post(`${this.path}/refresh`, this.auth.refresh); + this.router.post(`${this.path}/refresh`, AuthMiddleware ,this.auth.refresh); + this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); + this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); } } diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts new file mode 100644 index 0000000..271aa47 --- /dev/null +++ b/src/services/googleAuth.service.ts @@ -0,0 +1,25 @@ +import { CreateGoogleUsersDto } from "@/dtos/googleUsers.dto"; +import { User } from "@/interfaces"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +export const createInitialProfileGoogle = async (newUserData: CreateGoogleUsersDto):Promise =>{ + try { + const username = newUserData.email.split('@')[0]; + const createdUser: User = await prisma.user.create({ + data: { + email: newUserData.email, + name: newUserData.name, + username, + phone: '', + gender: "MALE", + date_of_birth: new Date('2000-01-01'), + password_hash: '', + }, + }); + return createdUser; + } catch (error) { + console.error("Error creating initial Google user profile:", error); + } +} diff --git a/src/utils/passsportGoogle.ts b/src/utils/passsportGoogle.ts new file mode 100644 index 0000000..54aa041 --- /dev/null +++ b/src/utils/passsportGoogle.ts @@ -0,0 +1,50 @@ +import passport from 'passport'; +import { Strategy as GoogleStrategy, Profile } from 'passport-google-oauth20'; +import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL } from '@/config'; +import { PrismaClient } from '@prisma/client'; +import { CreateGoogleUsersDto } from '@/dtos/googleUsers.dto'; +import { createInitialProfileGoogle } from '@/services/googleAuth.service'; +import { User } from '@/interfaces'; + +const prisma = new PrismaClient(); + +passport.use(new GoogleStrategy({ + clientID: GOOGLE_CLIENT_ID, + clientSecret: GOOGLE_CLIENT_SECRET, + callbackURL: GOOGLE_CALLBACK_URL +}, + // This "verify" function is called when Google successfully authenticates the user. + // 'profile' contains the user's Google profile information. + // 'done' is a callback you must call to tell Passport the authentication is complete. + async (accessToken, refreshToken, profile: Profile, done) => { + try { + console.log("Google profile:", profile); + + // Extract email from Google profile + const email = profile.emails?.[0]?.value; + const name = profile.displayName; + + if (!email) { + return done(new Error('No email found in Google profile'), undefined); + } + + // Find user in database by email + const user = await prisma.user.findUnique({ + where: { email } + }); + + if (!user) { + const newGoogleUserData: CreateGoogleUsersDto = { + email, + name, + }; + const createdUser:User = await createInitialProfileGoogle(newGoogleUserData); + return done(null, createdUser); + } + return done(null, user); + } catch (error) { + console.error('Error in Google authentication:', error); + return done(error as Error, undefined); + } + } +)); From 20d86b8f3490bfea1c030cfdb0fcc1a1f0a5ec89 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 2 Nov 2025 23:45:45 +0200 Subject: [PATCH 014/210] adjusted sign up to be on two stages / two pages to complete the profile --- src/controllers/auth.controller.ts | 18 ++++++++- src/dtos/users.dto.ts | 26 ++++++------- src/routes/auth.route.ts | 2 + src/services/auth.service.ts | 61 +++++++++++++++++++++--------- 4 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 1e18d1a..c14692c 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -11,9 +11,11 @@ export class AuthController { public signUp = async (req: Request, res: Response, next: NextFunction): Promise => { try { const userData: CreateUserDto = req.body; - const signUpUserData: User = await this.auth.signup(userData); + const { createdUserData, cookies} = await this.auth.signup(userData); - res.status(201).json({ data: signUpUserData, message: 'Signed Up Successfully' }); + res.setHeader('Set-Cookie', cookies); + + res.status(201).json({ data: createdUserData, message: 'Signed Up Successfully' }); } catch (error) { next(error); } @@ -64,4 +66,16 @@ export class AuthController { next(error); } }; + + public completeProfile = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const userData: User = req.user; + const profileData = req.body; + const updatedUserData: User = await this.auth.completeProfile(userData, profileData); + + res.status(200).json({ data: updatedUserData, message: 'Profile Completed Successfully' }); + } catch (error) { + next(error); + } + }; } diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index e4752fb..b5343aa 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -1,6 +1,5 @@ import { Gender } from '@prisma/client'; -import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDate, IsBoolean, IsOptional } from 'class-validator'; -import { Type } from 'class-transformer'; +import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDateString, IsBoolean, IsOptional } from 'class-validator'; export class CreateUserDto { @IsEmail() @@ -10,19 +9,6 @@ export class CreateUserDto { @IsNotEmpty() public name: string; - @IsString() - @IsNotEmpty() - public phone: string; - - @IsString() - @IsNotEmpty() - public gender: Gender; - - @IsNotEmpty() - @Type(() => Date) - @IsDate() - public date_of_birth: Date; - @IsString() @IsNotEmpty() @MinLength(8) @@ -52,5 +38,15 @@ export class UpdateUserDto { } export class CompleteUserProfileDto { + @IsString() + @IsNotEmpty() + public phone: string; + @IsString() + @IsNotEmpty() + public gender: Gender; + + @IsNotEmpty() + @IsDateString() + public date_of_birth: string; } diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index a1af644..97aac8f 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -21,6 +21,8 @@ export class AuthRoute implements Routes { this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); this.router.post(`${this.path}/refresh`, AuthMiddleware ,this.auth.refresh); + this.router.patch(`${this.path}/complete-profile`, AuthMiddleware, this.auth.completeProfile); + this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index b44488d..5346f64 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -3,7 +3,7 @@ import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EXPIRY } from '@config'; -import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; +import { CompleteUserProfileDto, CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; @@ -14,10 +14,10 @@ export class AuthService { public users = new PrismaClient().user; public refreshTokens = new PrismaClient().refreshToken; - public async signup(userData: CreateUserDto): Promise { - const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); + public async signup(userData: CreateUserDto): Promise<{ createdUserData:User; cookies: string[] }> { + const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); if (findUserSameEmail) throw new HttpException(409, `This email ${userData.email} already exists`); - + const emailHandle = userData.email.split('@')[0]; const findUserSameUsername: User = await this.users.findUnique({ where: { username: emailHandle } }); if (findUserSameUsername) throw new HttpException(409, `This username ${emailHandle} already exists`); @@ -25,9 +25,17 @@ export class AuthService { const hashedPassword = await hash(userData.password, 10); const username = emailHandle; const { password, ...userDataWithoutPassword } = userData; - const createUserData: Promise = this.users.create({ data: { ...userDataWithoutPassword, username ,password_hash: hashedPassword } }); + const createdUserData: User = await this.users.create({ + data: { + ...userDataWithoutPassword, username, password_hash: hashedPassword, + phone: "", gender: "MALE", date_of_birth: new Date("2000-01-01") + } + }); - return createUserData; + const tokenResponse = await this.createTokens(createdUserData, true); + const cookies = this.createCookies(tokenResponse); + + return { createdUserData, cookies }; } public async login(userData: LoginUserDto): Promise<{ cookies: string[]; findUser: User }> { @@ -56,14 +64,31 @@ export class AuthService { return findUser; } + public async completeProfile(userData: User, profileData: CompleteUserProfileDto): Promise { + const findUser: User = await this.users.findUnique({ where: { id: userData.id } }); + if (!findUser) throw new HttpException(409, "User doesn't exist"); + + const updatedUserData: User = await this.users.update({ + where: { id: userData.id }, + data: { + phone: profileData.phone, + gender: profileData.gender, + date_of_birth: new Date(profileData.date_of_birth), + }, + }); + + return updatedUserData; + } + + public async createTokens(user: User, rememberMe: boolean = false): Promise { const accessToken = this.createAccessToken(user); - + if (rememberMe) { const refreshToken = await this.createRefreshToken(user); return { accessToken, refreshToken }; } - + return { accessToken }; } @@ -79,12 +104,12 @@ export class AuthService { const dataStoredInToken: DataStoredInToken = { id: user.id }; const secretKey: string = REFRESH_TOKEN_SECRET; const expiresIn: number = this.parseTimeToSeconds(REFRESH_TOKEN_EXPIRY); - + const token = sign(dataStoredInToken, secretKey, { expiresIn }); - + // Hash the token before storing const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); - + // Store refresh token in database await this.refreshTokens.create({ data: { @@ -99,15 +124,15 @@ export class AuthService { public createCookies(tokenResponse: TokenResponse): string[] { const cookies: string[] = []; - + // Access token cookie cookies.push(`Authorization=${tokenResponse.accessToken.token}; HttpOnly; Max-Age=${tokenResponse.accessToken.expiresIn}; Path=/; SameSite=Strict`); - + // Refresh token cookie (if exists) if (tokenResponse.refreshToken) { cookies.push(`RefreshToken=${tokenResponse.refreshToken.token}; HttpOnly; Max-Age=${tokenResponse.refreshToken.expiresIn}; Path=/; SameSite=Strict`); } - + return cookies; } @@ -118,10 +143,10 @@ export class AuthService { // Verify the refresh token const secretKey: string = REFRESH_TOKEN_SECRET; const decoded = verify(refreshToken, secretKey) as DataStoredInToken; - + // Hash the token to compare with stored hash const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); - + // Check if refresh token exists and is not revoked const storedToken = await this.refreshTokens.findFirst({ where: { @@ -150,7 +175,7 @@ export class AuthService { // public async revokeRefreshToken(refreshToken: string): Promise { // const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); - + // await this.refreshTokens.updateMany({ // where: { token_hash: tokenHash, is_revoked: false }, // data: { is_revoked: true, revoked_at: new Date() }, @@ -160,7 +185,7 @@ export class AuthService { private parseTimeToSeconds(timeString: string): number { const unit = timeString.slice(-1); const value = parseInt(timeString.slice(0, -1)); - + switch (unit) { case 's': return value; case 'm': return value * 60; From e674496fcd8df22bb2407a6fa72c074f65b4b707 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Mon, 3 Nov 2025 23:43:12 +0200 Subject: [PATCH 015/210] added otp handling and updating phone number for google sign ups --- package-lock.json | 1626 +++++++++++++++-- package.json | 3 + src/config/index.ts | 3 +- src/controllers/auth.controller.ts | 28 +- src/controllers/googleAuth.controller.ts | 14 + src/dtos/googleUsers.dto.ts | 9 + .../20251103213136_otp_schema/migration.sql | 4 + src/prisma/schema.prisma | 76 +- src/routes/auth.route.ts | 5 +- src/services/auth.service.ts | 75 +- src/services/googleAuth.service.ts | 53 +- src/utils/nodeMailerService.ts | 10 + src/utils/passsportGoogle.ts | 6 +- 13 files changed, 1714 insertions(+), 198 deletions(-) create mode 100644 src/prisma/migrations/20251103213136_otp_schema/migration.sql create mode 100644 src/utils/nodeMailerService.ts diff --git a/package-lock.json b/package-lock.json index d03e6ba..c06f700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", @@ -47,6 +48,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", "@types/node": "^24.9.2", + "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", @@ -54,6 +56,7 @@ "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", "cross-env": "^10.1.0", + "dotenv-cli": "^11.0.0", "eslint": "^9.39.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", @@ -110,6 +113,693 @@ "openapi-types": ">=7" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.922.0.tgz", + "integrity": "sha512-cowHCdzir4KmT/MoRyp2RV3BAebjcpiyKU1pidu2D1lI87iGXlxNG7KXJ0W8mjQoGpKa2XcihDY/mtqd/6uVlA==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/credential-provider-node": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/signature-v4-multi-region": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.922.0.tgz", + "integrity": "sha512-jdHs7uy7cSpiMvrxhYmqHyJxgK7hyqw4plG8OQ4YTBpq0SbfAxdoOuOkwJ1IVUUQho4otR1xYYjiX/8e8J8qwQ==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.922.0.tgz", + "integrity": "sha512-EvfP4cqJfpO3L2v5vkIlTkMesPtRwWlMfsaW6Tpfm7iYfBOuTi6jx60pMDMTyJNVfh6cGmXwh/kj1jQdR+w99Q==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@aws-sdk/xml-builder": "3.921.0", + "@smithy/core": "^3.17.2", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.922.0.tgz", + "integrity": "sha512-WikGQpKkROJSK3D3E7odPjZ8tU7WJp5/TgGdRuZw3izsHUeH48xMv6IznafpRTmvHcjAbDQj4U3CJZNAzOK/OQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.922.0.tgz", + "integrity": "sha512-i72DgHMK7ydAEqdzU0Duqh60Q8W59EZmRJ73y0Y5oFmNOqnYsAI+UXyOoCsubp+Dkr6+yOwAn1gPt1XGE9Aowg==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-stream": "^4.5.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.922.0.tgz", + "integrity": "sha512-bVF+pI5UCLNkvbiZr/t2fgTtv84s8FCdOGAPxQiQcw5qOZywNuuCCY3wIIchmQr6GJr8YFkEp5LgDCac5EC5aQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/credential-provider-env": "3.922.0", + "@aws-sdk/credential-provider-http": "3.922.0", + "@aws-sdk/credential-provider-process": "3.922.0", + "@aws-sdk/credential-provider-sso": "3.922.0", + "@aws-sdk/credential-provider-web-identity": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.922.0.tgz", + "integrity": "sha512-agCwaD6mBihToHkjycL8ObIS2XOnWypWZZWhJSoWyHwFrhEKz1zGvgylK9Dc711oUfU+zU6J8e0JPKNJMNb3BQ==", + "dev": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.922.0", + "@aws-sdk/credential-provider-http": "3.922.0", + "@aws-sdk/credential-provider-ini": "3.922.0", + "@aws-sdk/credential-provider-process": "3.922.0", + "@aws-sdk/credential-provider-sso": "3.922.0", + "@aws-sdk/credential-provider-web-identity": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.922.0.tgz", + "integrity": "sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.922.0.tgz", + "integrity": "sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==", + "dev": true, + "dependencies": { + "@aws-sdk/client-sso": "3.922.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/token-providers": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.922.0.tgz", + "integrity": "sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.922.0.tgz", + "integrity": "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.922.0.tgz", + "integrity": "sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.922.0.tgz", + "integrity": "sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@aws/lambda-invoke-store": "^0.1.1", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.922.0.tgz", + "integrity": "sha512-ygg8lME1oFAbsH42ed2wtGqfHLoT5irgx6VC4X98j79fV1qXEwwwbqMsAiMQ/HJehpjqAFRVsHox3MHLN48Z5A==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.17.2", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-stream": "^4.5.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.922.0.tgz", + "integrity": "sha512-N4Qx/9KP3oVQBJOrSghhz8iZFtUC2NNeSZt88hpPhbqAEAtuX8aD8OzVcpnAtrwWqy82Yd2YTxlkqMGkgqnBsQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@smithy/core": "^3.17.2", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.922.0.tgz", + "integrity": "sha512-uYvKCF1TGh/MuJ4TMqmUM0Csuao02HawcseG4LUDyxdUsd/EFuxalWq1Cx4fKZQ2K8F504efZBjctMAMNY+l7A==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.922.0.tgz", + "integrity": "sha512-44Y/rNNwhngR2KHp6gkx//TOr56/hx6s4l+XLjOqH7EBCHL7XhnrT1y92L+DLiroVr1tCSmO8eHQwBv0Y2+mvw==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.922.0.tgz", + "integrity": "sha512-mmsgEEL5pE+A7gFYiJMDBCLVciaXq4EFI5iAP7bPpnHvOplnNOYxVy2IreKMllGvrfjVyLnwxzZYlo5zZ65FWg==", + "dev": true, + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.922.0.tgz", + "integrity": "sha512-/inmPnjZE0ZBE16zaCowAvouSx05FJ7p6BQYuzlJ8vxEU0sS0Hf8fvhuiRnN9V9eDUPIBY+/5EjbMWygXL4wlQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.922.0.tgz", + "integrity": "sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.922.0.tgz", + "integrity": "sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-endpoints": "^3.2.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.922.0.tgz", + "integrity": "sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.922.0.tgz", + "integrity": "sha512-NrPe/Rsr5kcGunkog0eBV+bY0inkRELsD2SacC4lQZvZiXf8VJ2Y7j+Yq1tB+h+FPLsdt3v9wItIvDf/laAm0Q==", + "dev": true, + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.921.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.921.0.tgz", + "integrity": "sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.1.tgz", + "integrity": "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "dev": true, @@ -1577,201 +2267,781 @@ "dev": true, "license": "MIT" }, - "node_modules/@pm2/io/node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/@pm2/io/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@pm2/io/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@pm2/js-api": { + "version": "0.8.0", + "dev": true, + "license": "Apache-2", + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^7.0.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + } + }, + "node_modules/@prisma/client": { + "version": "6.18.0", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.18.0", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.18.0", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.18.0", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.18.0", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.18.0", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.18.0" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.4.tgz", + "integrity": "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.1.tgz", + "integrity": "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.2.tgz", + "integrity": "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==", + "dev": true, + "dependencies": { + "@smithy/middleware-serde": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-stream": "^4.5.5", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.4.tgz", + "integrity": "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.5.tgz", + "integrity": "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==", + "dev": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.4", + "@smithy/querystring-builder": "^4.2.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.4.tgz", + "integrity": "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.4.tgz", + "integrity": "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.4.tgz", + "integrity": "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==", + "dev": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.6.tgz", + "integrity": "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==", + "dev": true, + "dependencies": { + "@smithy/core": "^3.17.2", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-middleware": "^4.2.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.6.tgz", + "integrity": "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/service-error-classification": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.4.tgz", + "integrity": "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==", + "dev": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.4.tgz", + "integrity": "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.4.tgz", + "integrity": "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==", + "dev": true, + "dependencies": { + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.4.tgz", + "integrity": "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==", + "dev": true, + "dependencies": { + "@smithy/abort-controller": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/querystring-builder": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.4.tgz", + "integrity": "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.4.tgz", + "integrity": "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.4.tgz", + "integrity": "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.4.tgz", + "integrity": "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.4.tgz", + "integrity": "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.4.tgz", + "integrity": "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.4.tgz", + "integrity": "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.2.tgz", + "integrity": "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==", "dev": true, - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "@smithy/core": "^3.17.2", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-stream": "^4.5.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/semver": { - "version": "7.5.4", + "node_modules/@smithy/types": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.1.tgz", + "integrity": "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==", "dev": true, - "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", + "node_modules/@smithy/url-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.4.tgz", + "integrity": "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==", "dev": true, - "license": "Apache-2.0" + "dependencies": { + "@smithy/querystring-parser": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pm2/io/node_modules/yallist": { - "version": "4.0.0", + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "dev": true, - "license": "ISC" + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pm2/js-api": { - "version": "0.8.0", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "dev": true, - "license": "Apache-2", "dependencies": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=4.0" + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/async": { - "version": "2.6.4", + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "dev": true, - "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/debug": { - "version": "4.3.7", + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "dev": true, - "license": "MIT" + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.5.tgz", + "integrity": "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "^4.3.1" + "@smithy/property-provider": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/client": { - "version": "6.18.0", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.7.tgz", + "integrity": "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw==", + "dev": true, + "dependencies": { + "@smithy/config-resolver": "^4.4.1", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/config": { - "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/@smithy/util-endpoints": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.4.tgz", + "integrity": "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==", + "dev": true, "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/debug": { - "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "6.18.0", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/fetch-engine": "6.18.0", - "@prisma/get-platform": "6.18.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/engines-version": { - "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/@smithy/util-middleware": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.4.tgz", + "integrity": "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/get-platform": "6.18.0" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/get-platform": { - "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/@smithy/util-retry": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.4.tgz", + "integrity": "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0" + "@smithy/service-error-classification": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "hasInstallScript": true, - "license": "Apache-2.0" - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", + "node_modules/@smithy/util-stream": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.5.tgz", + "integrity": "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==", "dev": true, - "license": "MIT" + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "type-detect": "4.0.8" + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@so-ric/colorspace": { @@ -2162,6 +3432,16 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-fC8w49YQ868IuPWRXqPfLf+MuTRex5Z1qxMoG8rr70riqqbOp2F5xgOKE9fODEBPzpnvjkJXFgK6IL2xgMSTnA==", + "dev": true, + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, "node_modules/@types/oauth": { "version": "0.9.6", "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", @@ -3154,6 +4434,12 @@ "node": ">=18" } }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "dev": true + }, "node_modules/brace-expansion": { "version": "2.0.2", "dev": true, @@ -4123,6 +5409,48 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", + "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^17.1.0", + "dotenv-expand": "^12.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -4896,6 +6224,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.1", "dev": true, @@ -7542,6 +8888,14 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.10", "dev": true, @@ -9577,6 +10931,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/strtok3": { "version": "10.3.4", "dev": true, diff --git a/package.json b/package.json index a6f76af..5e0b0de 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", @@ -60,6 +61,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", "@types/node": "^24.9.2", + "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", @@ -67,6 +69,7 @@ "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", "cross-env": "^10.1.0", + "dotenv-cli": "^11.0.0", "eslint": "^9.39.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", diff --git a/src/config/index.ts b/src/config/index.ts index 525b0b4..d575f3f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -3,6 +3,7 @@ config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); export const CREDENTIALS = process.env.CREDENTIALS === 'true'; export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET, - GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL } = process.env; + GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL, + GMAIL_USER, GMAIL_APP_PASSWORD } = process.env; export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index c14692c..fc11d96 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -11,10 +11,12 @@ export class AuthController { public signUp = async (req: Request, res: Response, next: NextFunction): Promise => { try { const userData: CreateUserDto = req.body; - const { createdUserData, cookies} = await this.auth.signup(userData); + const { createdUserData, cookies } = await this.auth.signup(userData); res.setHeader('Set-Cookie', cookies); + await this.auth.sendEmailOtp(userData.email); + res.status(201).json({ data: createdUserData, message: 'Signed Up Successfully' }); } catch (error) { next(error); @@ -26,7 +28,7 @@ export class AuthController { const userData: LoginUserDto = req.body; const { cookies, findUser } = await this.auth.login(userData); console.log(cookies); - + res.setHeader('Set-Cookie', cookies); res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); } catch (error) { @@ -52,15 +54,15 @@ export class AuthController { const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); res.setHeader('Set-Cookie', cookies); - res.status(200).json({ - data: { - user, + res.status(200).json({ + data: { + user, accessToken: { expiresIn: accessToken.expiresIn, expiresAt: new Date(Date.now() + accessToken.expiresIn * 1000) } - }, - message: 'Token Refreshed Successfully' + }, + message: 'Token Refreshed Successfully' }); } catch (error) { next(error); @@ -78,4 +80,16 @@ export class AuthController { next(error); } }; + + public verifyOTP = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const email = await this.auth.getUserEmail(req) + const { otp } = req.body; + const isSuccessful = await this.auth.verifyEmailOtp(email, otp); + res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); + } catch (error) { + next(error); + } + }; + } diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 8a502c8..6dc8eec 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -3,9 +3,13 @@ import passport from "passport"; import { Container } from "typedi"; import { NextFunction, Request, Response } from "express"; import { User } from "@/interfaces/users.interface"; +import { RequestWithUser } from "@/interfaces"; +import { GoogleAuthService } from "@/services/googleAuth.service"; +import { UpdateGoogleUserPhoneDto } from "@/dtos/googleUsers.dto"; export class GoogleAuthController { public authService = Container.get(AuthService); + public googleAuthService = Container.get(GoogleAuthService); public googleOAuth = passport.authenticate('google', { scope: ['profile', 'email'], @@ -37,4 +41,14 @@ export class GoogleAuthController { } })(req, res, next); }; + + public updatePhoneNumber = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const phone: UpdateGoogleUserPhoneDto = req.body.phone; + await this.googleAuthService.updatePhoneNumber(req.user.id, phone.phone); + res.status(200).json({ message: 'Phone Number Updated Successfully' }); + } catch (error) { + next(error); + } + }; } \ No newline at end of file diff --git a/src/dtos/googleUsers.dto.ts b/src/dtos/googleUsers.dto.ts index ca7af30..3497afe 100644 --- a/src/dtos/googleUsers.dto.ts +++ b/src/dtos/googleUsers.dto.ts @@ -1,4 +1,13 @@ +import { IsNotEmpty, IsString, MaxLength } from "class-validator"; + export class CreateGoogleUsersDto { public email: string; public name: string; +} + +export class UpdateGoogleUserPhoneDto { + @IsNotEmpty() + @MaxLength(15) + @IsString() + public phone: string; } \ No newline at end of file diff --git a/src/prisma/migrations/20251103213136_otp_schema/migration.sql b/src/prisma/migrations/20251103213136_otp_schema/migration.sql new file mode 100644 index 0000000..9b5c3eb --- /dev/null +++ b/src/prisma/migrations/20251103213136_otp_schema/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Users" ADD COLUMN "email_OTP" VARCHAR(6), +ADD COLUMN "email_OTP_expires_at" TIMESTAMP(3), +ADD COLUMN "isVerified" BOOLEAN NOT NULL DEFAULT false; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index a7408ad..b9b0950 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -11,17 +11,20 @@ datasource db { } model User { - id String @id @default(uuid()) - name String @db.VarChar(255) - email String @unique @db.VarChar(255) - username String @unique @db.VarChar(255) - phone String @db.VarChar(20) - password_hash String @db.VarChar(255) - gender Gender - date_of_birth DateTime - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? + id String @id @default(uuid()) + name String @db.VarChar(255) + email String @unique @db.VarChar(255) + username String @unique @db.VarChar(255) + phone String @db.VarChar(20) + password_hash String @db.VarChar(255) + gender Gender + date_of_birth DateTime + isVerified Boolean @default(false) + email_OTP String? @db.VarChar(6) + email_OTP_expires_at DateTime? + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? // Relations patient Patient? @relation("UserAsPatient") @@ -41,9 +44,9 @@ model User { } model Doctor { - id String @id @default(uuid()) - specialization String @db.VarChar(255) - avg_time DateTime? @db.Time(0) + id String @id @default(uuid()) + specialization String @db.VarChar(255) + avg_time DateTime? @db.Time(0) // Relations user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) @@ -53,22 +56,22 @@ model Doctor { } model Patient { - id String @id @default(uuid()) - bc_address String @db.VarChar(255) - consent Boolean @default(false) - controlling_nurse_id String? + id String @id @default(uuid()) + bc_address String @db.VarChar(255) + consent Boolean @default(false) + controlling_nurse_id String? // Relations - user User @relation("UserAsPatient", fields: [id], references: [id], onDelete: Cascade) - controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id], onDelete: SetNull) + user User @relation("UserAsPatient", fields: [id], references: [id], onDelete: Cascade) + controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id], onDelete: SetNull) @@map("Patient") } model Appointment { id String @id @default(uuid()) - patient_id String? - doctor_id String? + patient_id String? + doctor_id String? scheduled_time DateTime is_online Boolean @default(false) is_completed Boolean @default(false) @@ -89,8 +92,8 @@ model Appointment { model Medication { id String @id @default(uuid()) - patient_id String - doctor_id String + patient_id String + doctor_id String treatment_name String @db.VarChar(255) category String @db.VarChar(100) medication_end_date DateTime @@ -113,8 +116,8 @@ model Medication { model ScanLab { id String @id @default(uuid()) - patient_id String - doctor_id String + patient_id String + doctor_id String name String @db.VarChar(255) scheduled_date DateTime? scheduled_time DateTime? @db.Time(0) @@ -167,7 +170,7 @@ model ClinicNurse { } model ClinicDoctor { - id String @id @default(uuid()) + id String @id @default(uuid()) clinic_id String doctor_id String @@ -182,7 +185,7 @@ model ClinicDoctor { model AuditLog { id String @id @default(uuid()) - user_id String + user_id String action Action bc_hash String @db.VarChar(255) created_at DateTime @default(now()) @@ -196,13 +199,13 @@ model AuditLog { } model RefreshToken { - id String @id @default(uuid()) - user_id String - token_hash String @db.VarChar(255) - expires_at DateTime - is_revoked Boolean @default(false) - created_at DateTime @default(now()) - revoked_at DateTime? + id String @id @default(uuid()) + user_id String + token_hash String @db.VarChar(255) + expires_at DateTime + is_revoked Boolean @default(false) + created_at DateTime @default(now()) + revoked_at DateTime? // Relations user User @relation("UserRefreshTokens", fields: [user_id], references: [id], onDelete: Cascade) @@ -213,7 +216,6 @@ model RefreshToken { @@map("RefreshTokens") } - enum ScanLabType { SCAN LAB @@ -238,4 +240,4 @@ enum Period { enum Gender { MALE FEMALE -} \ No newline at end of file +} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 97aac8f..ca08344 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -21,9 +21,12 @@ export class AuthRoute implements Routes { this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); this.router.post(`${this.path}/refresh`, AuthMiddleware ,this.auth.refresh); - this.router.patch(`${this.path}/complete-profile`, AuthMiddleware, this.auth.completeProfile); + this.router.patch(`${this.path}/complete-profile-info`, AuthMiddleware, this.auth.completeProfile); + this.router.patch(`${this.path}/verify-otp`, AuthMiddleware ,this.auth.verifyOTP); + this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); + this.router.patch(`${this.path}/google/update-phone`, AuthMiddleware, this.googleAuth.updatePhoneNumber); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 5346f64..ac1bb12 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -5,8 +5,9 @@ import { Service } from 'typedi'; import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EXPIRY } from '@config'; import { CompleteUserProfileDto, CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; -import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse } from '@interfaces/auth.interface'; +import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse, RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; +import { transporter } from '@/utils/nodeMailerService'; import crypto from 'crypto'; @Service() @@ -14,7 +15,7 @@ export class AuthService { public users = new PrismaClient().user; public refreshTokens = new PrismaClient().refreshToken; - public async signup(userData: CreateUserDto): Promise<{ createdUserData:User; cookies: string[] }> { + public async signup(userData: CreateUserDto): Promise<{ createdUserData: User; cookies: string[] }> { const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); if (findUserSameEmail) throw new HttpException(409, `This email ${userData.email} already exists`); @@ -195,6 +196,76 @@ export class AuthService { } } + public async sendEmailOtp(email: string): Promise { + + const otp = Math.floor(100000 + Math.random() * 900000).toString(); + const expiryDate = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes + + const userInfo: Partial = await this.users.update({ + where: { email }, + data: { + email_OTP: otp, + email_OTP_expires_at: expiryDate, + }, + select: { email: true } + }); + + const mailOptions = { + from: 'theshooter200306@gmail.com', + to: userInfo.email, + subject: 'Your Email Verification Code', + html: ` +
+

Email Verification

+

Hi there,

+

Thank you for registering. Please use the following code to verify your email address:

+

+ ${otp} +

+

This code will expire in 10 minutes.

+

If you did not request this, please ignore this email.

+
+ ` + }; + + await transporter.sendMail(mailOptions); + } + + public async getUserEmail(req: RequestWithUser): Promise { + const email = await this.users.findUnique({ + where: { id: req.user.id }, + select: { email: true } + }); + if (!email) throw new HttpException(404, "User email not found"); + return email.email; + } + + public async verifyEmailOtp(email: string, otp: string): Promise { + const user = await this.users.findUnique({ where: { email } }); + if (!user) throw new HttpException(404, "User not found"); + + if (user.email_OTP !== otp) { + throw new HttpException(400, "Invalid OTP"); + } + + if (user.email_OTP_expires_at && user.email_OTP_expires_at < new Date()) { + throw new HttpException(400, "OTP has expired"); + } + + // Clear OTP fields after successful verification + await this.users.update({ + where: { email }, + data: { + email_OTP: null, + email_OTP_expires_at: null, + isVerified: true, + }, + }); + + return true; + } + + // Keep old methods for backward compatibility public createToken(user: User): AccessTokenData { return this.createAccessToken(user); diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index 271aa47..cfbf579 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -1,25 +1,42 @@ import { CreateGoogleUsersDto } from "@/dtos/googleUsers.dto"; import { User } from "@/interfaces"; import { PrismaClient } from "@prisma/client"; +import { Service } from "typedi"; const prisma = new PrismaClient(); -export const createInitialProfileGoogle = async (newUserData: CreateGoogleUsersDto):Promise =>{ - try { - const username = newUserData.email.split('@')[0]; - const createdUser: User = await prisma.user.create({ - data: { - email: newUserData.email, - name: newUserData.name, - username, - phone: '', - gender: "MALE", - date_of_birth: new Date('2000-01-01'), - password_hash: '', - }, - }); - return createdUser; - } catch (error) { - console.error("Error creating initial Google user profile:", error); +@Service() +export class GoogleAuthService { + + public async createInitialProfileGoogle(newUserData: CreateGoogleUsersDto): Promise { + try { + const username = newUserData.email.split('@')[0]; + const createdUser: User = await prisma.user.create({ + data: { + email: newUserData.email, + name: newUserData.name, + username, + phone: '', + gender: "MALE", + date_of_birth: new Date('2000-01-01'), + password_hash: '', + }, + }); + return createdUser; + } catch (error) { + console.error("Error creating initial Google user profile:", error); + } + } + + public async updatePhoneNumber(userId: string, phone: string): Promise { + try { + await prisma.user.update({ + where: { id: userId }, + data: { phone }, + }); + } catch (error) { + console.error("Error updating phone number:", error); + throw error; + } } -} +} \ No newline at end of file diff --git a/src/utils/nodeMailerService.ts b/src/utils/nodeMailerService.ts new file mode 100644 index 0000000..4532f68 --- /dev/null +++ b/src/utils/nodeMailerService.ts @@ -0,0 +1,10 @@ +import nodemailer from 'nodemailer'; +import { GMAIL_USER, GMAIL_APP_PASSWORD } from '@config'; + +export const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: GMAIL_USER, + pass: GMAIL_APP_PASSWORD, + }, +}); diff --git a/src/utils/passsportGoogle.ts b/src/utils/passsportGoogle.ts index 54aa041..2646829 100644 --- a/src/utils/passsportGoogle.ts +++ b/src/utils/passsportGoogle.ts @@ -3,10 +3,12 @@ import { Strategy as GoogleStrategy, Profile } from 'passport-google-oauth20'; import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL } from '@/config'; import { PrismaClient } from '@prisma/client'; import { CreateGoogleUsersDto } from '@/dtos/googleUsers.dto'; -import { createInitialProfileGoogle } from '@/services/googleAuth.service'; +import { GoogleAuthService } from '@/services/googleAuth.service'; import { User } from '@/interfaces'; +import Container from 'typedi'; const prisma = new PrismaClient(); +const googleAuthService = Container.get(GoogleAuthService); passport.use(new GoogleStrategy({ clientID: GOOGLE_CLIENT_ID, @@ -38,7 +40,7 @@ passport.use(new GoogleStrategy({ email, name, }; - const createdUser:User = await createInitialProfileGoogle(newGoogleUserData); + const createdUser:User = await googleAuthService.createInitialProfileGoogle(newGoogleUserData); return done(null, createdUser); } return done(null, user); From 0669ca44519706bb0f8dc529500a880bbe6ab006 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 4 Nov 2025 00:39:29 +0200 Subject: [PATCH 016/210] Added forget password functionality --- src/config/index.ts | 3 +- src/controllers/auth.controller.ts | 30 ++++++++- src/dtos/users.dto.ts | 12 ++++ .../migration.sql | 3 + src/prisma/schema.prisma | 30 ++++----- src/routes/auth.route.ts | 14 +++-- src/services/auth.service.ts | 61 ++++++++++++++++++- 7 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 src/prisma/migrations/20251103223535_reset_password_schema/migration.sql diff --git a/src/config/index.ts b/src/config/index.ts index d575f3f..9390b95 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -4,6 +4,7 @@ config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); export const CREDENTIALS = process.env.CREDENTIALS === 'true'; export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL, - GMAIL_USER, GMAIL_APP_PASSWORD } = process.env; + GMAIL_USER, GMAIL_APP_PASSWORD, + FRONTEND_URL, SENDER_EMAIL } = process.env; export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index fc11d96..30f10fc 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -3,7 +3,7 @@ import { Container } from 'typedi'; import { RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { AuthService } from '@services/auth.service'; -import { CreateUserDto, LoginUserDto } from '@/dtos/users.dto'; +import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@/dtos/users.dto'; export class AuthController { public auth = Container.get(AuthService); @@ -72,7 +72,7 @@ export class AuthController { public completeProfile = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { try { const userData: User = req.user; - const profileData = req.body; + const profileData: CompleteUserProfileDto = req.body; const updatedUserData: User = await this.auth.completeProfile(userData, profileData); res.status(200).json({ data: updatedUserData, message: 'Profile Completed Successfully' }); @@ -85,6 +85,9 @@ export class AuthController { try { const email = await this.auth.getUserEmail(req) const { otp } = req.body; + if (!otp) { + throw new Error('OTP is required'); + } const isSuccessful = await this.auth.verifyEmailOtp(email, otp); res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); } catch (error) { @@ -92,4 +95,27 @@ export class AuthController { } }; + public forgetPassword = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const email = req.body.email; + if(!email){ + throw new Error('Email is required'); + } + await this.auth.sendPasswordResetEmail(email); + res.status(200).json({ message: 'Password Reset Email Sent Successfully' }); + } catch (error) { + next(error); + } + }; + + public resetPassword = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const { token, newPassword }: ResetPasswordDto = req.body; + + await this.auth.resetPassword(token, newPassword); + res.status(200).json({ message: 'Password Reset Successfully' }); + } catch (error) { + next(error); + } + }; } diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index b5343aa..1940fc0 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -50,3 +50,15 @@ export class CompleteUserProfileDto { @IsDateString() public date_of_birth: string; } + +export class ResetPasswordDto { + @IsString() + @IsNotEmpty() + public token: string; + + @IsString() + @IsNotEmpty() + @MinLength(8) + @MaxLength(32) + public newPassword: string; +} \ No newline at end of file diff --git a/src/prisma/migrations/20251103223535_reset_password_schema/migration.sql b/src/prisma/migrations/20251103223535_reset_password_schema/migration.sql new file mode 100644 index 0000000..465f898 --- /dev/null +++ b/src/prisma/migrations/20251103223535_reset_password_schema/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Users" ADD COLUMN "password_reset_token" VARCHAR(255), +ADD COLUMN "password_reset_token_expires_at" TIMESTAMP(3); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index b9b0950..5029106 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -11,20 +11,22 @@ datasource db { } model User { - id String @id @default(uuid()) - name String @db.VarChar(255) - email String @unique @db.VarChar(255) - username String @unique @db.VarChar(255) - phone String @db.VarChar(20) - password_hash String @db.VarChar(255) - gender Gender - date_of_birth DateTime - isVerified Boolean @default(false) - email_OTP String? @db.VarChar(6) - email_OTP_expires_at DateTime? - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? + id String @id @default(uuid()) + name String @db.VarChar(255) + email String @unique @db.VarChar(255) + username String @unique @db.VarChar(255) + phone String @db.VarChar(20) + password_hash String @db.VarChar(255) + gender Gender + date_of_birth DateTime + isVerified Boolean @default(false) + email_OTP String? @db.VarChar(6) + email_OTP_expires_at DateTime? + password_reset_token String? @db.VarChar(255) + password_reset_token_expires_at DateTime? + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? // Relations patient Patient? @relation("UserAsPatient") diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ca08344..926ec6a 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -1,10 +1,11 @@ import { Router } from 'express'; import { AuthController } from '@controllers/auth.controller'; -import { CreateUserDto, LoginUserDto } from '@dtos/users.dto'; +import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@dtos/users.dto'; import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { GoogleAuthController } from '@/controllers/googleAuth.controller'; import { ValidationMiddleware } from '@middlewares/validation.middleware'; +import { UpdateGoogleUserPhoneDto } from '@/dtos/googleUsers.dto'; export class AuthRoute implements Routes { public path = '/auth'; @@ -20,13 +21,14 @@ export class AuthRoute implements Routes { this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); - this.router.post(`${this.path}/refresh`, AuthMiddleware ,this.auth.refresh); - this.router.patch(`${this.path}/complete-profile-info`, AuthMiddleware, this.auth.completeProfile); - this.router.patch(`${this.path}/verify-otp`, AuthMiddleware ,this.auth.verifyOTP); - + this.router.post(`${this.path}/refresh`, AuthMiddleware, this.auth.refresh); + this.router.patch(`${this.path}/complete-profile-info`, ValidationMiddleware(CompleteUserProfileDto), AuthMiddleware, this.auth.completeProfile); + this.router.patch(`${this.path}/verify-otp`, AuthMiddleware, this.auth.verifyOTP); + this.router.post(`${this.path}/forget-password`, this.auth.forgetPassword); + this.router.post(`${this.path}/reset-password`, ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword); this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); - this.router.patch(`${this.path}/google/update-phone`, AuthMiddleware, this.googleAuth.updatePhoneNumber); + this.router.patch(`${this.path}/google/update-phone`, ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index ac1bb12..5ce662f 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client'; import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; -import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EXPIRY } from '@config'; +import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EXPIRY, FRONTEND_URL, SENDER_EMAIL } from '@config'; import { CompleteUserProfileDto, CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse, RequestWithUser } from '@interfaces/auth.interface'; @@ -211,7 +211,7 @@ export class AuthService { }); const mailOptions = { - from: 'theshooter200306@gmail.com', + from: SENDER_EMAIL, to: userInfo.email, subject: 'Your Email Verification Code', html: ` @@ -265,6 +265,63 @@ export class AuthService { return true; } + public async sendPasswordResetEmail(email: string): Promise { + const user = await this.users.findUnique({ where: { email } }); + if (!user) throw new HttpException(200, "Email will be sent if account exists"); + + const resetPasswordToken = crypto.randomBytes(32).toString('hex'); + const resetPasswordTokenExpiry = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes + + await this.users.update({ + where: { email }, + data: { + password_reset_token: resetPasswordToken, + password_reset_token_expires_at: resetPasswordTokenExpiry, + }, + }); + + const resetLink = `${FRONTEND_URL}/reset-password?token=${resetPasswordToken}`; + const mailOptions = { + from: SENDER_EMAIL, + to: user.email, + subject: 'Your Password Reset Request', + html: ` +
+

Password Reset Request

+

You are receiving this email because you (or someone else) requested a password reset for your account.

+

Please click the button below to reset your password:

+ + Reset Your Password + +

If you did not request this, please ignore this email. This link is valid for 10 minutes.

+
+ ` + }; + await transporter.sendMail(mailOptions); + } + + public async resetPassword(token: string, newPassword: string): Promise { + const user = await this.users.findFirst({ + where: { + password_reset_token: token, + password_reset_token_expires_at: { gt: new Date() }, + }, + }); + + if (!user) throw new HttpException(400, "Invalid or expired password reset token"); + + const hashedPassword = await hash(newPassword, 10); + + await this.users.update({ + where: { id: user.id }, + data: { + password_hash: hashedPassword, + password_reset_token: null, + password_reset_token_expires_at: null, + }, + }); + } + // Keep old methods for backward compatibility public createToken(user: User): AccessTokenData { From 575d3cce406251bda08d04822e907e43d9df701c Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 4 Nov 2025 01:18:28 +0200 Subject: [PATCH 017/210] Updated Swagger docs --- src/app.ts | 14 +- src/dtos/users.dto.ts | 10 +- src/interfaces/users.interface.ts | 1 + src/server.ts | 1 - src/services/auth.service.ts | 14 +- swagger.yaml | 713 +++++++++++++++++++++++++----- 6 files changed, 635 insertions(+), 118 deletions(-) diff --git a/src/app.ts b/src/app.ts index 22d4aa8..629b1c3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -66,12 +66,19 @@ export class App { private initializeSwagger() { const options = { - swaggerDefinition: { + definition: { + openapi: '3.0.0', info: { - title: 'REST API', + title: 'GP Backend Authentication API', version: '1.0.0', - description: 'Example docs', + description: 'Comprehensive API documentation for authentication routes including email/password auth and Google OAuth', }, + servers: [ + { + url: `http://localhost:${this.port}`, + description: 'Development server', + }, + ], }, apis: ['swagger.yaml'], }; @@ -84,3 +91,4 @@ export class App { this.app.use(ErrorMiddleware); } } + diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 1940fc0..1f57160 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -8,6 +8,10 @@ export class CreateUserDto { @IsString() @IsNotEmpty() public name: string; + + @IsString() + @IsNotEmpty() + public phone: string; @IsString() @IsNotEmpty() @@ -24,9 +28,8 @@ export class LoginUserDto { @IsNotEmpty() public password: string; - @IsOptional() @IsBoolean() - public rememberMe?: boolean; + public rememberMe: boolean; } export class UpdateUserDto { @@ -38,9 +41,6 @@ export class UpdateUserDto { } export class CompleteUserProfileDto { - @IsString() - @IsNotEmpty() - public phone: string; @IsString() @IsNotEmpty() diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index d7a1a5e..cc0eabe 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -14,6 +14,7 @@ export interface User { gender: Gender; date_of_birth: Date; password_hash: string; + isVerified: boolean; created_at: Date; modified_at: Date; deleted_at?: Date; diff --git a/src/server.ts b/src/server.ts index d2a4e01..6422afb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,5 @@ import { App } from '@/app'; import { AuthRoute } from '@routes/auth.route'; -import { UserRoute } from '@routes/users.route'; import { ValidateEnv } from '@utils/validateEnv'; ValidateEnv(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 5ce662f..b2364f7 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -29,7 +29,7 @@ export class AuthService { const createdUserData: User = await this.users.create({ data: { ...userDataWithoutPassword, username, password_hash: hashedPassword, - phone: "", gender: "MALE", date_of_birth: new Date("2000-01-01") + gender: "MALE", date_of_birth: new Date("2000-01-01") } }); @@ -72,7 +72,6 @@ export class AuthService { const updatedUserData: User = await this.users.update({ where: { id: userData.id }, data: { - phone: profileData.phone, gender: profileData.gender, date_of_birth: new Date(profileData.date_of_birth), }, @@ -127,13 +126,12 @@ export class AuthService { const cookies: string[] = []; // Access token cookie - cookies.push(`Authorization=${tokenResponse.accessToken.token}; HttpOnly; Max-Age=${tokenResponse.accessToken.expiresIn}; Path=/; SameSite=Strict`); + cookies.push(`Authorization=${tokenResponse.accessToken.token}; HttpOnly; Max-Age=${tokenResponse.accessToken.expiresIn}; Path=/; SameSite=Lax`); // Refresh token cookie (if exists) if (tokenResponse.refreshToken) { - cookies.push(`RefreshToken=${tokenResponse.refreshToken.token}; HttpOnly; Max-Age=${tokenResponse.refreshToken.expiresIn}; Path=/; SameSite=Strict`); + cookies.push(`RefreshToken=${tokenResponse.refreshToken.token}; HttpOnly; Max-Age=${tokenResponse.refreshToken.expiresIn}; Path=/; SameSite=Lax`); } - return cookies; } @@ -307,9 +305,9 @@ export class AuthService { password_reset_token_expires_at: { gt: new Date() }, }, }); - + if (!user) throw new HttpException(400, "Invalid or expired password reset token"); - + const hashedPassword = await hash(newPassword, 10); await this.users.update({ @@ -321,7 +319,7 @@ export class AuthService { }, }); } - + // Keep old methods for backward compatibility public createToken(user: User): AccessTokenData { diff --git a/swagger.yaml b/swagger.yaml index eebcb31..5562f8b 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1,123 +1,634 @@ +openapi: 3.0.0 +info: + title: GP Backend Authentication API + description: Comprehensive API documentation for authentication routes including email/password auth and Google OAuth + version: 1.0.0 + contact: + name: API Support + email: support@gpbackend.com + +servers: + - url: http://localhost:3000 + description: Development server + - url: https://api.gpbackend.com + description: Production server + tags: -- name: users - description: users API + - name: Authentication + description: Email/Password authentication endpoints + - name: Google OAuth + description: Google OAuth authentication endpoints paths: -# [GET] users - /users: - get: + /auth/signup: + post: tags: - - users - summary: Find All Users + - Authentication + summary: User registration + description: Register a new user with email, name, and password. Sends OTP email for verification. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserDto' + example: + email: user@example.com + name: John Doe + password: SecurePass123 + phone: '+1234567890' responses: - 200: - description: 'OK' - 500: - description: 'Server Error' + '201': + description: User successfully registered + headers: + Set-Cookie: + schema: + type: string + example: Authorization=eyJhbGc...; Path=/; HttpOnly; RefreshToken=eyJhbGc...; Path=/; HttpOnly + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + message: + type: string + example: Signed Up Successfully + '400': + $ref: '#/components/responses/BadRequest' + '409': + description: User already exists + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: User with this email already exists -# [POST] users + /auth/login: post: tags: - - users - summary: Add User - parameters: - - name: body - in: body - description: user Data + - Authentication + summary: User login + description: Authenticate user with email and password + requestBody: required: true - schema: - $ref: '#/definitions/users' + content: + application/json: + schema: + $ref: '#/components/schemas/LoginUserDto' + example: + email: user@example.com + password: SecurePass123 + rememberMe: true responses: - 201: - description: 'Created' - 400: - description: 'Bad Request' - 409: - description: 'Conflict' - 500: - description: 'Server Error' - -# [GET] users/id - /users/{id}: - get: + '200': + description: Login successful + headers: + Set-Cookie: + schema: + type: string + example: Authorization=eyJhbGc...; Path=/; HttpOnly; RefreshToken=eyJhbGc...; Path=/; HttpOnly + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + message: + type: string + example: Logged In Successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + description: Invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Invalid email or password + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: User not found + + /auth/logout: + post: + tags: + - Authentication + summary: User logout + description: Logout authenticated user and clear cookies + security: + - bearerAuth: [] + - cookieAuth: [] + responses: + '200': + description: Logout successful + headers: + Set-Cookie: + schema: + type: string + example: Authorization=; Max-age=0; RefreshToken=; Max-age=0 + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Logged Out Successfully + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/refresh: + post: + tags: + - Authentication + summary: Refresh access token + description: Generate new access token using refresh token from cookies + security: + - cookieAuth: [] + responses: + '200': + description: Token refreshed successfully + headers: + Set-Cookie: + schema: + type: string + example: Authorization=eyJhbGc...; Path=/; HttpOnly + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + user: + $ref: '#/components/schemas/User' + accessToken: + type: object + properties: + expiresIn: + type: number + example: 3600 + expiresAt: + type: string + format: date-time + example: '2025-11-04T15:30:00.000Z' + message: + type: string + example: Token Refreshed Successfully + '401': + $ref: '#/components/responses/Unauthorized' + '403': + description: Invalid or expired refresh token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Invalid refresh token + + /auth/complete-profile-info: + patch: tags: - - users - summary: Find User By Id - parameters: - - name: id - in: path - description: User Id + - Authentication + summary: Complete user profile + description: Update user profile with phone number, gender, and date of birth + security: + - bearerAuth: [] + - cookieAuth: [] + requestBody: required: true - type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/CompleteUserProfileDto' + example: + gender: MALE + date_of_birth: '1990-01-15' responses: - 200: - description: 'OK' - 409: - description: 'Conflict' - 500: - description: 'Server Error' - -# [PUT] users/id - put: + '200': + description: Profile updated successfully + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + message: + type: string + example: Profile Completed Successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/verify-otp: + patch: tags: - - users - summary: Update User By Id - parameters: - - name: id - in: path - description: user Id + - Authentication + summary: Verify email OTP + description: Verify the OTP sent to user's email during registration + security: + - bearerAuth: [] + - cookieAuth: [] + requestBody: required: true - type: integer - - name: body - in: body - description: user Data + content: + application/json: + schema: + type: object + required: + - otp + properties: + otp: + type: string + description: 6-digit OTP code + example: '123456' + responses: + '200': + description: OTP verified successfully + content: + application/json: + schema: + type: object + properties: + data: + type: boolean + example: true + message: + type: string + example: OTP Verified Successfully + '400': + description: Invalid or expired OTP + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Invalid or expired OTP + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/forget-password: + post: + tags: + - Authentication + summary: Request password reset + description: Send password reset email with reset token + requestBody: required: true - schema: - $ref: '#/definitions/users' + content: + application/json: + schema: + type: object + required: + - email + properties: + email: + type: string + format: email + description: User's email address + example: user@example.com responses: - 200: - description: 'OK' - 400: - description: 'Bad Request' - 409: - description: 'Conflict' - 500: - description: 'Server Error' - -# [DELETE] users/id - delete: + '200': + description: Password reset email sent + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Password Reset Email Sent Successfully + '400': + description: Email is required + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Email is required + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: User not found + + /auth/reset-password: + post: tags: - - users - summary: Delete User By Id - parameters: - - name: id - in: path - description: user Id + - Authentication + summary: Reset password + description: Reset user password using the token from email + requestBody: required: true - type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/ResetPasswordDto' + example: + token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + newPassword: NewSecurePass123 responses: - 200: - description: 'OK' - 409: - description: 'Conflict' - 500: - description: 'Server Error' - -# definitions -definitions: - users: - type: object - required: + '200': + description: Password reset successful + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Password Reset Successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + description: Invalid or expired reset token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Invalid or expired reset token + + /auth/google: + get: + tags: + - Google OAuth + summary: Initiate Google OAuth + description: Redirect user to Google OAuth consent screen for authentication + responses: + '302': + description: Redirect to Google OAuth + headers: + Location: + schema: + type: string + example: https://accounts.google.com/o/oauth2/v2/auth?... + + # /auth/google/callback: + # get: + # tags: + # - Google OAuth + # summary: Google OAuth callback + # description: Handle Google OAuth callback and create user session + # parameters: + # - in: query + # name: code + # schema: + # type: string + # description: Authorization code from Google + # - in: query + # name: state + # schema: + # type: string + # description: State parameter for CSRF protection + # responses: + # '302': + # description: Redirect to dashboard on success or login on failure + # headers: + # Location: + # schema: + # type: string + # example: /dashboard + # Set-Cookie: + # schema: + # type: string + # example: Authorization=eyJhbGc...; Path=/; HttpOnly; RefreshToken=eyJhbGc...; Path=/; HttpOnly + # '401': + # description: Authentication failed + # headers: + # Location: + # schema: + # type: string + # example: /login + + /auth/google/update-phone: + patch: + tags: + - Google OAuth + summary: Update phone number for Google OAuth user + description: Add or update phone number for users who signed in with Google + security: + - bearerAuth: [] + - cookieAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateGoogleUserPhoneDto' + example: + phone: '+1234567890' + responses: + '200': + description: Phone number updated successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Phone Number Updated Successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: JWT access token in Authorization header + cookieAuth: + type: apiKey + in: cookie + name: Authorization + description: JWT access token in cookie + + schemas: + CreateUserDto: + type: object + required: - email + - name - password - properties: - email: - type: string - description: user Email - password: - type: string - description: user Password - -schemes: - - https - - http + - phone + properties: + email: + type: string + format: email + description: User's email address + name: + type: string + description: User's full name + minLength: 1 + phone: + type: string + description: User's phone number with country code + example: '+1234567890' + password: + type: string + format: password + description: User's password + minLength: 8 + maxLength: 32 + + LoginUserDto: + type: object + required: + - email + - password + - rememberMe + properties: + email: + type: string + format: email + description: User's email address + password: + type: string + format: password + description: User's password + rememberMe: + type: boolean + description: Keep user logged in for extended period + default: false + + CompleteUserProfileDto: + type: object + required: + - gender + - date_of_birth + properties: + gender: + type: string + enum: [MALE, FEMALE, OTHER] + description: User's gender + date_of_birth: + type: string + format: date + description: User's date of birth in ISO 8601 format + example: '1990-01-15' + + ResetPasswordDto: + type: object + required: + - token + - newPassword + properties: + token: + type: string + description: Password reset token from email + newPassword: + type: string + format: password + description: New password for the account + minLength: 8 + maxLength: 32 + + UpdateGoogleUserPhoneDto: + type: object + required: + - phone + properties: + phone: + type: string + description: User's phone number + maxLength: 15 + example: '+1234567890' + + User: + type: object + properties: + id: + type: string + format: uuid + description: Unique user identifier + email: + type: string + format: email + description: User's email address + name: + type: string + description: User's full name + phone: + type: string + nullable: true + description: User's phone number + gender: + type: string + enum: [MALE, FEMALE, OTHER] + nullable: true + description: User's gender + date_of_birth: + type: string + format: date + nullable: true + description: User's date of birth + isVerified: + type: boolean + description: Whether email is verified + created_at: + type: string + format: date-time + description: Account creation timestamp + updated_at: + type: string + format: date-time + description: Last update timestamp + + Error: + type: object + properties: + message: + type: string + description: Error message + statusCode: + type: integer + description: HTTP status code + errors: + type: array + items: + type: object + description: Validation errors (if any) + + responses: + BadRequest: + description: Bad request - validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Validation failed + statusCode: 400 + errors: + - field: email + message: Invalid email format + + Unauthorized: + description: Unauthorized - authentication required + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + message: Unauthorized + statusCode: 401 From fbd374970bd8486621e7e432563e362e2a85bd79 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 5 Nov 2025 17:11:57 +0200 Subject: [PATCH 018/210] added bilingual error support --- src/dtos/users.dto.ts | 7 +- src/exceptions/HttpException.ts | 4 +- src/middlewares/auth.middleware.ts | 10 ++- src/middlewares/error.middleware.ts | 6 +- src/middlewares/validation.middleware.ts | 5 +- src/services/auth.service.ts | 84 ++++++++++++++++----- src/services/googleAuth.service.ts | 7 +- src/utils/errorMessages.ts | 94 ++++++++++++++++++++++++ src/utils/passsportGoogle.ts | 14 +++- swagger.yaml | 61 ++++++++------- 10 files changed, 232 insertions(+), 60 deletions(-) create mode 100644 src/utils/errorMessages.ts diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 1f57160..2b1629b 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -21,9 +21,10 @@ export class CreateUserDto { } export class LoginUserDto { - @IsEmail() - public email: string; - + @IsString() + @IsNotEmpty() + public emailOrUsername: string; + @IsString() @IsNotEmpty() public password: string; diff --git a/src/exceptions/HttpException.ts b/src/exceptions/HttpException.ts index f0ae6aa..553048b 100644 --- a/src/exceptions/HttpException.ts +++ b/src/exceptions/HttpException.ts @@ -1,10 +1,12 @@ export class HttpException extends Error { public status: number; public message: string; + public messageAr?: string; - constructor(status: number, message: string) { + constructor(status: number, message: string, messageAr?: string) { super(message); this.status = status; this.message = message; + this.messageAr = messageAr; } } diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index ec89d21..c9ccf9b 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -5,6 +5,7 @@ import { SECRET_KEY } from '@config'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@/interfaces'; +import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; const getAuthorization = (req: Request) => { const cookie = req.cookies['Authorization']; @@ -29,12 +30,15 @@ export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: req.user = findUser; next(); } else { - next(new HttpException(401, 'Wrong authentication token')); + const error = createBilingualError(401, ErrorMessages.WRONG_AUTHENTICATION_TOKEN); + next(new HttpException(error.status, error.message, error.messageAr)); } } else { - next(new HttpException(401, 'Authentication required')); + const error = createBilingualError(401, ErrorMessages.AUTHENTICATION_REQUIRED); + next(new HttpException(error.status, error.message, error.messageAr)); } } catch (error) { - next(new HttpException(401, 'Wrong authentication token')); + const err = createBilingualError(401, ErrorMessages.WRONG_AUTHENTICATION_TOKEN); + next(new HttpException(err.status, err.message, err.messageAr)); } }; diff --git a/src/middlewares/error.middleware.ts b/src/middlewares/error.middleware.ts index b8e1b8e..a9758eb 100644 --- a/src/middlewares/error.middleware.ts +++ b/src/middlewares/error.middleware.ts @@ -6,9 +6,13 @@ export const ErrorMiddleware = (error: HttpException, req: Request, res: Respons try { const status: number = error.status || 500; const message: string = error.message || 'Something went wrong'; + const messageAr: string = error.messageAr || 'حدث خطأ ما'; logger.error(`[${req.method}] ${req.path} >> StatusCode:: ${status}, Message:: ${message}`); - res.status(status).json({ message }); + res.status(status).json({ + messageEn : message, + messageAr + }); } catch (error) { next(error); } diff --git a/src/middlewares/validation.middleware.ts b/src/middlewares/validation.middleware.ts index ca6ee22..c9615c7 100644 --- a/src/middlewares/validation.middleware.ts +++ b/src/middlewares/validation.middleware.ts @@ -21,7 +21,10 @@ export const ValidationMiddleware = (type: any, skipMissingProperties = false, w }) .catch((errors: ValidationError[]) => { const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); - next(new HttpException(400, message)); + // For validation errors, we keep the detailed message in English and provide a generic Arabic message + // since validation constraints are typically defined in English + const messageAr = 'خطأ في التحقق من صحة البيانات المدخلة'; + next(new HttpException(400, message, messageAr)); }); }; }; diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index b2364f7..b18a650 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -8,6 +8,7 @@ import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse, RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { transporter } from '@/utils/nodeMailerService'; +import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import crypto from 'crypto'; @Service() @@ -17,11 +18,17 @@ export class AuthService { public async signup(userData: CreateUserDto): Promise<{ createdUserData: User; cookies: string[] }> { const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); - if (findUserSameEmail) throw new HttpException(409, `This email ${userData.email} already exists`); + if (findUserSameEmail) { + const error = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } const emailHandle = userData.email.split('@')[0]; const findUserSameUsername: User = await this.users.findUnique({ where: { username: emailHandle } }); - if (findUserSameUsername) throw new HttpException(409, `This username ${emailHandle} already exists`); + if (findUserSameUsername) { + const error = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } const hashedPassword = await hash(userData.password, 10); const username = emailHandle; @@ -40,11 +47,24 @@ export class AuthService { } public async login(userData: LoginUserDto): Promise<{ cookies: string[]; findUser: User }> { - const findUser: User = await this.users.findUnique({ where: { email: userData.email } }); - if (!findUser) throw new HttpException(409, `This email ${userData.email} was not found`); + const findUser: User = await this.users.findFirst({ + where: { + OR: [ + { email: userData.emailOrUsername }, + { username: userData.emailOrUsername } + ] + } + }); + if (!findUser) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); + throw new HttpException(error.status, error.message, error.messageAr); + } const isPasswordMatching: boolean = await compare(userData.password, findUser.password_hash); - if (!isPasswordMatching) throw new HttpException(409, 'Password is not matching'); + if (!isPasswordMatching) { + const error = createBilingualError(404, ErrorMessages.PASSWORD_NOT_MATCHING); + throw new HttpException(error.status, error.message, error.messageAr); + } const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); @@ -54,7 +74,10 @@ export class AuthService { public async logout(userData: User): Promise { const findUser: User = await this.users.findFirst({ where: { email: userData.email, password_hash: userData.password_hash } }); - if (!findUser) throw new HttpException(409, "User doesn't exist"); + if (!findUser) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_EXIST); + throw new HttpException(error.status, error.message, error.messageAr); + } // Revoke all refresh tokens for this user await this.refreshTokens.updateMany({ @@ -67,7 +90,10 @@ export class AuthService { public async completeProfile(userData: User, profileData: CompleteUserProfileDto): Promise { const findUser: User = await this.users.findUnique({ where: { id: userData.id } }); - if (!findUser) throw new HttpException(409, "User doesn't exist"); + if (!findUser) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_EXIST); + throw new HttpException(error.status, error.message, error.messageAr); + } const updatedUserData: User = await this.users.update({ where: { id: userData.id }, @@ -136,7 +162,10 @@ export class AuthService { } public async refreshAccessToken(refreshToken: string): Promise<{ cookies: string[]; user: User; accessToken: AccessTokenData }> { - if (!refreshToken) throw new HttpException(401, 'Refresh token not provided'); + if (!refreshToken) { + const error = createBilingualError(401, ErrorMessages.REFRESH_TOKEN_NOT_PROVIDED); + throw new HttpException(error.status, error.message, error.messageAr); + } try { // Verify the refresh token @@ -156,11 +185,17 @@ export class AuthService { }, }); - if (!storedToken) throw new HttpException(401, 'Invalid or expired refresh token'); + if (!storedToken) { + const error = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); + throw new HttpException(error.status, error.message, error.messageAr); + } // Get user const user = await this.users.findUnique({ where: { id: decoded.id } }); - if (!user) throw new HttpException(401, 'User not found'); + if (!user) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_EXIST); + throw new HttpException(error.status, error.message, error.messageAr); + } // Create new access token const accessToken = this.createAccessToken(user); @@ -168,7 +203,8 @@ export class AuthService { return { cookies, user, accessToken }; } catch (error) { - throw new HttpException(401, 'Invalid refresh token'); + const err = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); + throw new HttpException(err.status, err.message, err.messageAr); } } @@ -234,20 +270,28 @@ export class AuthService { where: { id: req.user.id }, select: { email: true } }); - if (!email) throw new HttpException(404, "User email not found"); + if (!email) { + const error = createBilingualError(404, ErrorMessages.USER_EMAIL_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } return email.email; } public async verifyEmailOtp(email: string, otp: string): Promise { const user = await this.users.findUnique({ where: { email } }); - if (!user) throw new HttpException(404, "User not found"); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_EXIST); + throw new HttpException(error.status, error.message, error.messageAr); + } if (user.email_OTP !== otp) { - throw new HttpException(400, "Invalid OTP"); + const error = createBilingualError(400, ErrorMessages.INVALID_OTP); + throw new HttpException(error.status, error.message, error.messageAr); } if (user.email_OTP_expires_at && user.email_OTP_expires_at < new Date()) { - throw new HttpException(400, "OTP has expired"); + const error = createBilingualError(400, ErrorMessages.OTP_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); } // Clear OTP fields after successful verification @@ -265,7 +309,10 @@ export class AuthService { public async sendPasswordResetEmail(email: string): Promise { const user = await this.users.findUnique({ where: { email } }); - if (!user) throw new HttpException(200, "Email will be sent if account exists"); + if (!user) { + const error = createBilingualError(200, ErrorMessages.EMAIL_SENT_IF_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } const resetPasswordToken = crypto.randomBytes(32).toString('hex'); const resetPasswordTokenExpiry = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes @@ -306,7 +353,10 @@ export class AuthService { }, }); - if (!user) throw new HttpException(400, "Invalid or expired password reset token"); + if (!user) { + const error = createBilingualError(400, ErrorMessages.INVALID_PASSWORD_RESET_TOKEN); + throw new HttpException(error.status, error.message, error.messageAr); + } const hashedPassword = await hash(newPassword, 10); diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index cfbf579..f88fd24 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -2,6 +2,8 @@ import { CreateGoogleUsersDto } from "@/dtos/googleUsers.dto"; import { User } from "@/interfaces"; import { PrismaClient } from "@prisma/client"; import { Service } from "typedi"; +import { HttpException } from "@/exceptions/HttpException"; +import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; const prisma = new PrismaClient(); @@ -25,6 +27,8 @@ export class GoogleAuthService { return createdUser; } catch (error) { console.error("Error creating initial Google user profile:", error); + const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); + throw new HttpException(err.status, err.message, err.messageAr); } } @@ -36,7 +40,8 @@ export class GoogleAuthService { }); } catch (error) { console.error("Error updating phone number:", error); - throw error; + const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); + throw new HttpException(err.status, err.message, err.messageAr); } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts new file mode 100644 index 0000000..32d6f4f --- /dev/null +++ b/src/utils/errorMessages.ts @@ -0,0 +1,94 @@ +export const ErrorMessages = { + // Authentication errors + EMAIL_EXISTS: { + en: `This email already exists`, + ar: `البريد الإلكتروني موجود بالفعل`, + }, + USERNAME_EXISTS: { + en: 'This username already exists', + ar: 'اسم المستخدم موجود بالفعل' + }, + USER_NOT_FOUND_CREDENTIALS: { + en: 'User with the provided credentials was not found', + ar: 'لم يتم العثور على المستخدم ببيانات الاعتماد المقدمة', + }, + PASSWORD_NOT_MATCHING: { + en: 'Password is not matching', + ar: 'كلمة المرور غير صحيحة', + }, + USER_NOT_EXIST: { + en: "User doesn't exist", + ar: 'المستخدم غير موجود', + }, + REFRESH_TOKEN_NOT_PROVIDED: { + en: 'Refresh token not provided', + ar: 'لم يتم تقديم رمز التحديث', + }, + INVALID_REFRESH_TOKEN: { + en: 'Invalid or expired refresh token', + ar: 'رمز التحديث غير صالح أو منتهي الصلاحية', + }, + USER_EMAIL_NOT_FOUND: { + en: 'User email not found', + ar: 'البريد الإلكتروني للمستخدم غير موجود', + }, + INVALID_OTP: { + en: 'Invalid OTP', + ar: 'رمز التحقق غير صالح', + }, + OTP_EXPIRED: { + en: 'OTP has expired', + ar: 'انتهت صلاحية رمز التحقق', + }, + EMAIL_SENT_IF_EXISTS: { + en: 'Email will be sent if account exists', + ar: 'سيتم إرسال البريد الإلكتروني إذا كان الحساب موجودًا', + }, + INVALID_PASSWORD_RESET_TOKEN: { + en: 'Invalid or expired password reset token', + ar: 'رمز إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية', + }, + + // Authentication middleware errors + WRONG_AUTHENTICATION_TOKEN: { + en: 'Wrong authentication token', + ar: 'رمز المصادقة غير صحيح', + }, + AUTHENTICATION_REQUIRED: { + en: 'Authentication required', + ar: 'المصادقة مطلوبة', + }, + + // Validation errors + VALIDATION_ERROR: { + en: 'Validation error', + ar: 'خطأ في التحقق من البيانات', + }, + + // Google Auth errors + NO_EMAIL_IN_GOOGLE_PROFILE: { + en: 'No email found in Google profile', + ar: 'لم يتم العثور على البريد الإلكتروني في ملف Google الشخصي', + }, + GOOGLE_AUTH_ERROR: { + en: 'Error in Google authentication', + ar: 'خطأ في المصادقة عبر Google', + }, + + // Generic errors + SOMETHING_WENT_WRONG: { + en: 'Something went wrong', + ar: 'حدث خطأ ما', + }, +}; + +// Helper function to create bilingual error +export const createBilingualError = ( + status: number, + messageObj: { en: string; ar: string }, +) => { + const message = messageObj.en; + const messageAr = messageObj.ar; + + return { status, message, messageAr }; +}; diff --git a/src/utils/passsportGoogle.ts b/src/utils/passsportGoogle.ts index 2646829..ff513f7 100644 --- a/src/utils/passsportGoogle.ts +++ b/src/utils/passsportGoogle.ts @@ -6,6 +6,12 @@ import { CreateGoogleUsersDto } from '@/dtos/googleUsers.dto'; import { GoogleAuthService } from '@/services/googleAuth.service'; import { User } from '@/interfaces'; import Container from 'typedi'; +import { ErrorMessages } from '@/utils/errorMessages'; + +// Define a custom error type with Arabic message support +interface BilingualError extends Error { + messageAr?: string; +} const prisma = new PrismaClient(); const googleAuthService = Container.get(GoogleAuthService); @@ -27,7 +33,9 @@ passport.use(new GoogleStrategy({ const name = profile.displayName; if (!email) { - return done(new Error('No email found in Google profile'), undefined); + const error: BilingualError = new Error(ErrorMessages.NO_EMAIL_IN_GOOGLE_PROFILE.en); + error.messageAr = ErrorMessages.NO_EMAIL_IN_GOOGLE_PROFILE.ar; + return done(error, undefined); } // Find user in database by email @@ -46,7 +54,9 @@ passport.use(new GoogleStrategy({ return done(null, user); } catch (error) { console.error('Error in Google authentication:', error); - return done(error as Error, undefined); + const err: BilingualError = new Error(ErrorMessages.GOOGLE_AUTH_ERROR.en); + err.messageAr = ErrorMessages.GOOGLE_AUTH_ERROR.ar; + return done(err, undefined); } } )); diff --git a/swagger.yaml b/swagger.yaml index 5562f8b..1d52901 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -64,7 +64,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: User with this email already exists + messageEn: This email already exists + messageAr: البريد الإلكتروني موجود بالفعل /auth/login: post: @@ -79,7 +80,7 @@ paths: schema: $ref: '#/components/schemas/LoginUserDto' example: - email: user@example.com + emailOrUsername: user@example.com password: SecurePass123 rememberMe: true responses: @@ -109,7 +110,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: Invalid email or password + messageEn: Password is not matching + messageAr: كلمة المرور غير صحيحة '404': description: User not found content: @@ -117,7 +119,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: User not found + messageEn: User with the provided credentials was not found + messageAr: لم يتم العثور على المستخدم ببيانات الاعتماد المقدمة /auth/logout: post: @@ -195,7 +198,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: Invalid refresh token + messageEn: Invalid or expired refresh token + messageAr: رمز التحديث غير صالح أو منتهي الصلاحية /auth/complete-profile-info: patch: @@ -276,7 +280,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: Invalid or expired OTP + messageEn: Invalid OTP + messageAr: رمز التحقق غير صالح '401': $ref: '#/components/responses/Unauthorized' @@ -318,7 +323,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: Email is required + messageEn: Validation error + messageAr: خطأ في التحقق من البيانات '404': description: User not found content: @@ -326,7 +332,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: User not found + messageEn: User email not found + messageAr: البريد الإلكتروني للمستخدم غير موجود /auth/reset-password: post: @@ -363,7 +370,8 @@ paths: schema: $ref: '#/components/schemas/Error' example: - message: Invalid or expired reset token + messageEn: Invalid or expired password reset token + messageAr: رمز إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية /auth/google: get: @@ -494,14 +502,13 @@ components: LoginUserDto: type: object required: - - email + - emailOrUsername - password - rememberMe properties: - email: + emailOrUsername: type: string - format: email - description: User's email address + description: User's email address or username password: type: string format: password @@ -574,7 +581,7 @@ components: description: User's phone number gender: type: string - enum: [MALE, FEMALE, OTHER] + enum: [MALE, FEMALE] nullable: true description: User's gender date_of_birth: @@ -597,17 +604,12 @@ components: Error: type: object properties: - message: + messageEn: + type: string + description: Error message in English + messageAr: type: string - description: Error message - statusCode: - type: integer - description: HTTP status code - errors: - type: array - items: - type: object - description: Validation errors (if any) + description: Error message in Arabic responses: BadRequest: @@ -617,11 +619,8 @@ components: schema: $ref: '#/components/schemas/Error' example: - message: Validation failed - statusCode: 400 - errors: - - field: email - message: Invalid email format + messageEn: Validation error + messageAr: خطأ في التحقق من البيانات Unauthorized: description: Unauthorized - authentication required @@ -630,5 +629,5 @@ components: schema: $ref: '#/components/schemas/Error' example: - message: Unauthorized - statusCode: 401 + messageEn: Authentication required + messageAr: المصادقة مطلوبة From 7974ab37e421b43fb62687fb91c8e734d896935f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 7 Nov 2025 21:44:57 +0200 Subject: [PATCH 019/210] update redirect URL in Google OAuth callback to use environment variable --- src/controllers/googleAuth.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 6dc8eec..535d6ae 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -25,17 +25,17 @@ export class GoogleAuthController { if (!user) { return res.redirect('/login'); } - + try { // Generate JWT tokens for Google OAuth user const tokenResponse = await this.authService.createTokens(user, true); const cookies = this.authService.createCookies(tokenResponse); - + // Set JWT cookies res.setHeader('Set-Cookie', cookies); - + // Redirect to dashboard with success - res.redirect('/dashboard'); + res.redirect(`${process.env.FRONTEND_URL}/dashboard`); } catch (error) { next(error); } From df589a0fee7f5193fe8df4b7280abf3344fd094f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 7 Nov 2025 22:04:41 +0200 Subject: [PATCH 020/210] update redirect URL in Google OAuth callback to complete profile page --- src/controllers/googleAuth.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 535d6ae..8e65f6d 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -35,7 +35,7 @@ export class GoogleAuthController { res.setHeader('Set-Cookie', cookies); // Redirect to dashboard with success - res.redirect(`${process.env.FRONTEND_URL}/dashboard`); + res.redirect(`${process.env.FRONTEND_URL}/complete-profile`); } catch (error) { next(error); } From 62873469a1ff9f650a04c25809d579a9aae7b4f4 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 7 Nov 2025 22:30:06 +0200 Subject: [PATCH 021/210] added isNewUser flag to determine redirection after google oauth --- src/controllers/googleAuth.controller.ts | 13 ++++++++++--- src/utils/passsportGoogle.ts | 6 ++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 8e65f6d..3b559ba 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -18,7 +18,7 @@ export class GoogleAuthController { public googleOAuthCallback = (req: Request, res: Response, next: NextFunction) => { passport.authenticate('google', { failureRedirect: '/login', - }, async (err, user: User, info) => { + }, async (err, user: User, info: { isNewUser?: boolean }) => { if (err) { return next(err); } @@ -34,8 +34,15 @@ export class GoogleAuthController { // Set JWT cookies res.setHeader('Set-Cookie', cookies); - // Redirect to dashboard with success - res.redirect(`${process.env.FRONTEND_URL}/complete-profile`); + // Check if it's a new user from the info object + const isNewUser = info?.isNewUser || false; + + // Redirect based on whether it's first time or not + if (isNewUser) { + res.redirect(`${process.env.FRONTEND_URL}/complete-profile`); + } else { + res.redirect(`${process.env.FRONTEND_URL}/dashboard`); + } } catch (error) { next(error); } diff --git a/src/utils/passsportGoogle.ts b/src/utils/passsportGoogle.ts index ff513f7..eda4d2d 100644 --- a/src/utils/passsportGoogle.ts +++ b/src/utils/passsportGoogle.ts @@ -49,9 +49,11 @@ passport.use(new GoogleStrategy({ name, }; const createdUser:User = await googleAuthService.createInitialProfileGoogle(newGoogleUserData); - return done(null, createdUser); + // Pass isNewUser flag in the info object + return done(null, createdUser, { isNewUser: true }); } - return done(null, user); + // Existing user - not new + return done(null, user, { isNewUser: false }); } catch (error) { console.error('Error in Google authentication:', error); const err: BilingualError = new Error(ErrorMessages.GOOGLE_AUTH_ERROR.en); From be4d84c492525ece09f5855b22ffa0dd85fc5035 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 9 Nov 2025 23:31:42 +0200 Subject: [PATCH 022/210] Added endpoint for getting user data after google login --- src/controllers/auth.controller.ts | 1 + src/controllers/googleAuth.controller.ts | 11 +++- src/interfaces/users.interface.ts | 9 ++++ src/routes/auth.route.ts | 1 + src/services/auth.service.ts | 17 +++++-- src/services/googleAuth.service.ts | 28 +++++++++- src/utils/errorMessages.ts | 5 ++ swagger.yaml | 65 +++++++++++++++++++++++- 8 files changed, 130 insertions(+), 7 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 30f10fc..c03856a 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -119,3 +119,4 @@ export class AuthController { } }; } + diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 3b559ba..e160a97 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -2,7 +2,7 @@ import { AuthService } from "@/services/auth.service"; import passport from "passport"; import { Container } from "typedi"; import { NextFunction, Request, Response } from "express"; -import { User } from "@/interfaces/users.interface"; +import { User, UserLoginData } from "@/interfaces/users.interface"; import { RequestWithUser } from "@/interfaces"; import { GoogleAuthService } from "@/services/googleAuth.service"; import { UpdateGoogleUserPhoneDto } from "@/dtos/googleUsers.dto"; @@ -58,4 +58,13 @@ export class GoogleAuthController { next(error); } }; + + public getGoogleUserData = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const googleUserData: UserLoginData = await this.googleAuthService.getGoogleUserData(req.user.id); + res.status(200).json({ data: googleUserData, message: 'Google User Data Retrieved Successfully' }); + } catch (error) { + next(error); + } + }; } \ No newline at end of file diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index cc0eabe..41923eb 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -51,3 +51,12 @@ export interface Doctor { clinic_doctors?: ClinicDoctor[]; } +export interface UserLoginData { + name: string, + email: string, + username: string, + phone: string, + gender: Gender, + date_of_birth: Date, + isVerified: Boolean, +} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 926ec6a..885867f 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -30,5 +30,6 @@ export class AuthRoute implements Routes { this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); this.router.patch(`${this.path}/google/update-phone`, ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber); + this.router.get(`${this.path}/google/userData`, AuthMiddleware, this.googleAuth.getGoogleUserData); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index b18a650..e22915e 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -6,7 +6,7 @@ import { SECRET_KEY, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY, ACCESS_TOKEN_EX import { CompleteUserProfileDto, CreateUserDto, LoginUserDto } from '@dtos/users.dto'; import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, AccessTokenData, RefreshTokenData, TokenResponse, RequestWithUser } from '@interfaces/auth.interface'; -import { User } from '@interfaces/users.interface'; +import { UserLoginData, User } from '@interfaces/users.interface'; import { transporter } from '@/utils/nodeMailerService'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import crypto from 'crypto'; @@ -46,7 +46,7 @@ export class AuthService { return { createdUserData, cookies }; } - public async login(userData: LoginUserDto): Promise<{ cookies: string[]; findUser: User }> { + public async login(userData: LoginUserDto): Promise<{ cookies: string[]; findUser: UserLoginData }> { const findUser: User = await this.users.findFirst({ where: { OR: [ @@ -66,10 +66,21 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } + const { name, gender, date_of_birth, email, isVerified, username, phone } = findUser; + const patientLoginData: UserLoginData = { + name, + email, + username, + phone, + gender, + date_of_birth, + isVerified + }; + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); - return { cookies, findUser }; + return { cookies, findUser: patientLoginData }; } public async logout(userData: User): Promise { diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index f88fd24..29b75d4 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -1,5 +1,5 @@ import { CreateGoogleUsersDto } from "@/dtos/googleUsers.dto"; -import { User } from "@/interfaces"; +import { User, UserLoginData } from "@/interfaces"; import { PrismaClient } from "@prisma/client"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; @@ -44,4 +44,30 @@ export class GoogleAuthService { throw new HttpException(err.status, err.message, err.messageAr); } } + + public async getGoogleUserData(userId: string): Promise { + try { + const user: UserLoginData | null = await prisma.user.findUnique({ + where: { id: userId }, + select: { + email: true, + name: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + isVerified: true, + } + }); + if (!user) { + const err = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(err.status, err.message, err.messageAr); + } + return user; + } catch (error) { + console.error("Error retrieving Google user data:", error); + const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); + throw new HttpException(err.status, err.message, err.messageAr); + } + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 32d6f4f..88861ba 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -1,5 +1,10 @@ export const ErrorMessages = { // Authentication errors + USER_NOT_FOUND: { + en: 'User not found', + ar: 'المستخدم غير موجود', + }, + EMAIL_EXISTS: { en: `This email already exists`, ar: `البريد الإلكتروني موجود بالفعل`, diff --git a/swagger.yaml b/swagger.yaml index 1d52901..0dc2ca8 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -97,7 +97,7 @@ paths: type: object properties: data: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/PatientLoginData' message: type: string example: Logged In Successfully @@ -458,6 +458,40 @@ paths: '401': $ref: '#/components/responses/Unauthorized' + /auth/google/userData: + get: + tags: + - Google OAuth + summary: Get Google OAuth user data + description: Retrieve authenticated Google user's profile information + security: + - bearerAuth: [] + - cookieAuth: [] + responses: + '200': + description: User data retrieved successfully + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/PatientLoginData' + message: + type: string + example: Google User Data Retrieved Successfully + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + messageEn: User not found + messageAr: المستخدم غير موجود + components: securitySchemes: bearerAuth: @@ -526,7 +560,7 @@ components: properties: gender: type: string - enum: [MALE, FEMALE, OTHER] + enum: [MALE, FEMALE] description: User's gender date_of_birth: type: string @@ -601,6 +635,33 @@ components: format: date-time description: Last update timestamp + PatientLoginData: + type: object + properties: + name: + type: string + description: User's full name + email: + type: string + format: email + description: User's email address + username: + type: string + description: User's username + phone: + type: string + description: User's phone number + gender: + type: string + enum: [MALE, FEMALE] + date_of_birth: + type: string + format: date + description: User's date of birth + isVerified: + type: boolean + description: Whether email is verified + Error: type: object properties: From d1cb0bf26e95f6f55f8b7f894c4d47ac1ea12248 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 9 Nov 2025 23:44:12 +0200 Subject: [PATCH 023/210] added resend otp api --- src/controllers/auth.controller.ts | 10 ++++++++++ src/routes/auth.route.ts | 1 + src/services/auth.service.ts | 2 +- swagger.yaml | 32 ++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index c03856a..e79f3e0 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -118,5 +118,15 @@ export class AuthController { next(error); } }; + + public resendOTP = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try { + const email = await this.auth.getUserEmail(req) + await this.auth.sendEmailOtp(email); + res.status(200).json({ message: 'OTP Resent Successfully' }); + } catch (error) { + next(error); + } + } } diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 885867f..5d714eb 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -26,6 +26,7 @@ export class AuthRoute implements Routes { this.router.patch(`${this.path}/verify-otp`, AuthMiddleware, this.auth.verifyOTP); this.router.post(`${this.path}/forget-password`, this.auth.forgetPassword); this.router.post(`${this.path}/reset-password`, ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword); + this.router.post(`${this.path}/resend-otp`, AuthMiddleware, this.auth.resendOTP); this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index e22915e..05a0453 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -275,7 +275,7 @@ export class AuthService { await transporter.sendMail(mailOptions); } - + public async getUserEmail(req: RequestWithUser): Promise { const email = await this.users.findUnique({ where: { id: req.user.id }, diff --git a/swagger.yaml b/swagger.yaml index 0dc2ca8..c925b62 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -285,6 +285,38 @@ paths: '401': $ref: '#/components/responses/Unauthorized' + /auth/resend-otp: + post: + tags: + - Authentication + summary: Resend OTP + description: Resend OTP verification code to authenticated user's email + security: + - bearerAuth: [] + - cookieAuth: [] + responses: + '200': + description: OTP resent successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: OTP Resent Successfully + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + messageEn: User not found + messageAr: المستخدم غير موجود + /auth/forget-password: post: tags: From 092fe2df47ef33fd1a61b16bf3c4ee49f2fa29ed Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Mon, 10 Nov 2025 19:35:52 +0200 Subject: [PATCH 024/210] fix: phone number of google account was not updated --- src/controllers/googleAuth.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index e160a97..82e65ac 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -51,8 +51,8 @@ export class GoogleAuthController { public updatePhoneNumber = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { try { - const phone: UpdateGoogleUserPhoneDto = req.body.phone; - await this.googleAuthService.updatePhoneNumber(req.user.id, phone.phone); + const phone: string = req.body.phone; + await this.googleAuthService.updatePhoneNumber(req.user.id, phone); res.status(200).json({ message: 'Phone Number Updated Successfully' }); } catch (error) { next(error); From f8fb5e36ade44ff5017f50dc4bcc4f4464dad141 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 12 Nov 2025 00:13:27 +0200 Subject: [PATCH 025/210] fix: logout endpoint fixed to delete cookies --- src/controllers/auth.controller.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index e79f3e0..bdf4b0a 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -41,7 +41,12 @@ export class AuthController { const userData: User = req.user; const logOutUserData: User = await this.auth.logout(userData); - res.setHeader('Set-Cookie', ['Authorization=; Max-age=0', 'RefreshToken=; Max-age=0']); + res.setHeader('Set-Cookie', [ + 'Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax', + 'RefreshToken=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax' + ]); + console.log(res.getHeaders()); + res.status(200).json({ message: 'Logged Out Successfully' }); } catch (error) { next(error); @@ -98,7 +103,7 @@ export class AuthController { public forgetPassword = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { try { const email = req.body.email; - if(!email){ + if (!email) { throw new Error('Email is required'); } await this.auth.sendPasswordResetEmail(email); From dd42ae028347a6f9e4aa1edf6863ab35533d6b1c Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 12 Nov 2025 00:53:39 +0200 Subject: [PATCH 026/210] added user as patient to patient table and made google made account verified auto --- src/controllers/auth.controller.ts | 2 -- src/controllers/googleAuth.controller.ts | 1 - src/dtos/googleUsers.dto.ts | 1 + src/interfaces/users.interface.ts | 3 ++- .../20251111224133_added_user_role/migration.sql | 5 +++++ src/prisma/schema.prisma | 8 ++++++++ src/services/auth.service.ts | 12 +++++++++++- src/services/googleAuth.service.ts | 8 ++++++++ src/utils/passsportGoogle.ts | 4 ++-- 9 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/prisma/migrations/20251111224133_added_user_role/migration.sql diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index bdf4b0a..87e6bd0 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -27,7 +27,6 @@ export class AuthController { try { const userData: LoginUserDto = req.body; const { cookies, findUser } = await this.auth.login(userData); - console.log(cookies); res.setHeader('Set-Cookie', cookies); res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); @@ -45,7 +44,6 @@ export class AuthController { 'Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax', 'RefreshToken=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax' ]); - console.log(res.getHeaders()); res.status(200).json({ message: 'Logged Out Successfully' }); } catch (error) { diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index 82e65ac..d204b9e 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -5,7 +5,6 @@ import { NextFunction, Request, Response } from "express"; import { User, UserLoginData } from "@/interfaces/users.interface"; import { RequestWithUser } from "@/interfaces"; import { GoogleAuthService } from "@/services/googleAuth.service"; -import { UpdateGoogleUserPhoneDto } from "@/dtos/googleUsers.dto"; export class GoogleAuthController { public authService = Container.get(AuthService); diff --git a/src/dtos/googleUsers.dto.ts b/src/dtos/googleUsers.dto.ts index 3497afe..404b1ea 100644 --- a/src/dtos/googleUsers.dto.ts +++ b/src/dtos/googleUsers.dto.ts @@ -3,6 +3,7 @@ import { IsNotEmpty, IsString, MaxLength } from "class-validator"; export class CreateGoogleUsersDto { public email: string; public name: string; + public isEmailVerified: boolean; } export class UpdateGoogleUserPhoneDto { diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 41923eb..f28f63d 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -3,7 +3,7 @@ import { Medication } from './medications.interface'; import { ScanLab } from './scans-labs.interface'; import { ClinicNurse, ClinicDoctor } from './clinics.interface'; import { AuditLog } from './audit-logs.interface'; -import { Gender } from '@prisma/client'; +import { Gender, Role } from '@prisma/client'; export interface User { id: string; @@ -13,6 +13,7 @@ export interface User { phone: string; gender: Gender; date_of_birth: Date; + role: Role; password_hash: string; isVerified: boolean; created_at: Date; diff --git a/src/prisma/migrations/20251111224133_added_user_role/migration.sql b/src/prisma/migrations/20251111224133_added_user_role/migration.sql new file mode 100644 index 0000000..c2ea822 --- /dev/null +++ b/src/prisma/migrations/20251111224133_added_user_role/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('ADMIN', 'DOCTOR', 'NURSE', 'PATIENT'); + +-- AlterTable +ALTER TABLE "Users" ADD COLUMN "role" "Role" NOT NULL DEFAULT 'PATIENT'; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 5029106..98b6df7 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -19,6 +19,7 @@ model User { password_hash String @db.VarChar(255) gender Gender date_of_birth DateTime + role Role @default(PATIENT) isVerified Boolean @default(false) email_OTP String? @db.VarChar(6) email_OTP_expires_at DateTime? @@ -243,3 +244,10 @@ enum Gender { MALE FEMALE } + +enum Role { + ADMIN + DOCTOR + NURSE + PATIENT +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 05a0453..e87d661 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient, Role } from '@prisma/client'; import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; @@ -14,6 +14,7 @@ import crypto from 'crypto'; @Service() export class AuthService { public users = new PrismaClient().user; + public patients = new PrismaClient().patient; public refreshTokens = new PrismaClient().refreshToken; public async signup(userData: CreateUserDto): Promise<{ createdUserData: User; cookies: string[] }> { @@ -36,10 +37,19 @@ export class AuthService { const createdUserData: User = await this.users.create({ data: { ...userDataWithoutPassword, username, password_hash: hashedPassword, + role: Role.PATIENT, gender: "MALE", date_of_birth: new Date("2000-01-01") } }); + await this.patients.create({ + data: { + id: createdUserData.id, + bc_address: '', + consent: false, + } + }); + const tokenResponse = await this.createTokens(createdUserData, true); const cookies = this.createCookies(tokenResponse); diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index 29b75d4..486e4f4 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -17,6 +17,7 @@ export class GoogleAuthService { data: { email: newUserData.email, name: newUserData.name, + isVerified: newUserData.isEmailVerified, username, phone: '', gender: "MALE", @@ -24,6 +25,13 @@ export class GoogleAuthService { password_hash: '', }, }); + await prisma.patient.create({ + data: { + id: createdUser.id, + bc_address: '', + consent: false, + } + }); return createdUser; } catch (error) { console.error("Error creating initial Google user profile:", error); diff --git a/src/utils/passsportGoogle.ts b/src/utils/passsportGoogle.ts index eda4d2d..55bcbc1 100644 --- a/src/utils/passsportGoogle.ts +++ b/src/utils/passsportGoogle.ts @@ -26,11 +26,10 @@ passport.use(new GoogleStrategy({ // 'done' is a callback you must call to tell Passport the authentication is complete. async (accessToken, refreshToken, profile: Profile, done) => { try { - console.log("Google profile:", profile); - // Extract email from Google profile const email = profile.emails?.[0]?.value; const name = profile.displayName; + const isEmailVerified = profile.emails?.[0]?.verified; if (!email) { const error: BilingualError = new Error(ErrorMessages.NO_EMAIL_IN_GOOGLE_PROFILE.en); @@ -47,6 +46,7 @@ passport.use(new GoogleStrategy({ const newGoogleUserData: CreateGoogleUsersDto = { email, name, + isEmailVerified: isEmailVerified || false, }; const createdUser:User = await googleAuthService.createInitialProfileGoogle(newGoogleUserData); // Pass isNewUser flag in the info object From 9f797522c5fa994ac2ca655968b1e7f236f924c5 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 12 Nov 2025 20:14:38 +0200 Subject: [PATCH 027/210] added hasCompletedProfile boolean and handled it in the APIs --- src/interfaces/users.interface.ts | 2 ++ .../migration.sql | 2 ++ src/prisma/schema.prisma | 1 + src/services/auth.service.ts | 8 +++++--- src/services/googleAuth.service.ts | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/prisma/migrations/20251112181411_added_has_completed_profile/migration.sql diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index f28f63d..2c3a978 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -16,6 +16,7 @@ export interface User { role: Role; password_hash: string; isVerified: boolean; + hasCompletedProfile: boolean; created_at: Date; modified_at: Date; deleted_at?: Date; @@ -60,4 +61,5 @@ export interface UserLoginData { gender: Gender, date_of_birth: Date, isVerified: Boolean, + hasCompletedProfile: Boolean, } diff --git a/src/prisma/migrations/20251112181411_added_has_completed_profile/migration.sql b/src/prisma/migrations/20251112181411_added_has_completed_profile/migration.sql new file mode 100644 index 0000000..6f3ee04 --- /dev/null +++ b/src/prisma/migrations/20251112181411_added_has_completed_profile/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Users" ADD COLUMN "hasCompletedProfile" BOOLEAN NOT NULL DEFAULT false; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 98b6df7..e2bc5c7 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -21,6 +21,7 @@ model User { date_of_birth DateTime role Role @default(PATIENT) isVerified Boolean @default(false) + hasCompletedProfile Boolean @default(false) email_OTP String? @db.VarChar(6) email_OTP_expires_at DateTime? password_reset_token String? @db.VarChar(255) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index e87d661..ad5814b 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -76,7 +76,7 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - const { name, gender, date_of_birth, email, isVerified, username, phone } = findUser; + const { name, gender, date_of_birth, email, isVerified, username, phone, hasCompletedProfile } = findUser; const patientLoginData: UserLoginData = { name, email, @@ -84,9 +84,10 @@ export class AuthService { phone, gender, date_of_birth, - isVerified + isVerified, + hasCompletedProfile }; - + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); @@ -121,6 +122,7 @@ export class AuthService { data: { gender: profileData.gender, date_of_birth: new Date(profileData.date_of_birth), + hasCompletedProfile: true, }, }); diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index 486e4f4..b5b13cb 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -65,6 +65,7 @@ export class GoogleAuthService { gender: true, date_of_birth: true, isVerified: true, + hasCompletedProfile: true, } }); if (!user) { From 39028576a99e1528ccc56b809a873cf4ff91844f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 12 Nov 2025 22:08:24 +0200 Subject: [PATCH 028/210] updated swagger for hasCompletedProfile boolean --- swagger.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index c925b62..6f8b36c 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -658,6 +658,10 @@ components: isVerified: type: boolean description: Whether email is verified + hasCompletedProfile: + type: boolean + description: Whether user has completed profile + default: false created_at: type: string format: date-time @@ -693,6 +697,10 @@ components: isVerified: type: boolean description: Whether email is verified + hasCompletedProfile: + type: boolean + description: Whether user has completed profile + default: false Error: type: object From d5a58f756846d4e9d8bee1efc0492da8517e4d0e Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 27 Nov 2025 14:33:28 +0200 Subject: [PATCH 029/210] updated redirect after google auth for new users --- src/controllers/googleAuth.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index d204b9e..ce5d0c2 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -38,7 +38,7 @@ export class GoogleAuthController { // Redirect based on whether it's first time or not if (isNewUser) { - res.redirect(`${process.env.FRONTEND_URL}/complete-profile`); + res.redirect(`${process.env.FRONTEND_URL}/api/auth/google-callback`); } else { res.redirect(`${process.env.FRONTEND_URL}/dashboard`); } From 0a8013ec65f14db637e6b180ea8083347fe93f1c Mon Sep 17 00:00:00 2001 From: kareem Date: Fri, 28 Nov 2025 00:08:56 +0200 Subject: [PATCH 030/210] update redirect after google auth for a logged in user --- src/controllers/googleAuth.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index ce5d0c2..fb37847 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -40,7 +40,7 @@ export class GoogleAuthController { if (isNewUser) { res.redirect(`${process.env.FRONTEND_URL}/api/auth/google-callback`); } else { - res.redirect(`${process.env.FRONTEND_URL}/dashboard`); + res.redirect(`${process.env.FRONTEND_URL}/api/auth/google-callback`); } } catch (error) { next(error); From 7a64e96b2ab7df273b7b81d17b6fcde0be9122b5 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 29 Nov 2025 21:23:25 +0200 Subject: [PATCH 031/210] autogen --- package-lock.json | 366 ++++++++++++++++++++++++--- package.json | 5 +- src/app.ts | 23 +- src/controllers/fabric.controller.ts | 56 ++++ src/routes/auth.route.ts | 76 +++++- src/routes/fabric.route.ts | 44 ++++ src/routes/users.route.ts | 32 ++- src/server.ts | 4 +- src/services/fabric.service.ts | 129 ++++++++++ src/swagger-output.json | 334 ++++++++++++++++++++++++ src/swagger.js | 20 ++ swagger-output.json | 0 swagger.yaml | 74 ++++++ 13 files changed, 1085 insertions(+), 78 deletions(-) create mode 100644 src/controllers/fabric.controller.ts create mode 100644 src/routes/fabric.route.ts create mode 100644 src/services/fabric.service.ts create mode 100644 src/swagger-output.json create mode 100644 src/swagger.js create mode 100644 swagger-output.json diff --git a/package-lock.json b/package-lock.json index c06f700..463d611 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "license": "ISC", "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "^6.18.0", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", @@ -28,6 +30,7 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", + "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -47,7 +50,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", - "@types/node": "^24.9.2", + "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", @@ -1453,6 +1456,37 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", + "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "dev": true, @@ -1497,6 +1531,37 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", + "integrity": "sha512-q5lFrzbKsKdMgMGhaEE4dVXtpQa4qyWMdD1RXJFki6BiiKOzZC7IEV3xj67ffSaD33iYztxomYxlHVQJqD21HQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-protos": "^0.3.0", + "@noble/curves": "^1.9.4", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "pkcs11js": "^2.1.0" + } + }, + "node_modules/@hyperledger/fabric-protos": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", + "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.11.0", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=16.13.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -1560,7 +1625,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -1974,6 +2041,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "license": "MIT" @@ -2025,9 +2102,23 @@ "node": ">= 10" } }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", - "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -2427,6 +2518,70 @@ "@prisma/debug": "6.18.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@scarf/scarf": { "version": "1.4.0", "hasInstallScript": true, @@ -3425,8 +3580,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.2", - "dev": true, + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -4073,7 +4229,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4417,21 +4572,43 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.0", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bowser": { @@ -4871,7 +5048,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4884,7 +5060,6 @@ }, "node_modules/cliui/node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4892,12 +5067,10 @@ }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4905,7 +5078,6 @@ }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4918,7 +5090,6 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4929,7 +5100,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -4970,7 +5140,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4981,7 +5150,6 @@ }, "node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/color-string": { @@ -5282,7 +5450,6 @@ }, "node_modules/deepmerge": { "version": "4.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5630,7 +5797,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6588,7 +6754,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -6708,7 +6873,9 @@ "license": "MIT" }, "node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -6775,6 +6942,12 @@ "node": ">= 4" } }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, "node_modules/gopd": { "version": "1.2.0", "license": "MIT", @@ -7033,7 +7206,9 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -7888,7 +8063,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7936,7 +8113,6 @@ }, "node_modules/json5": { "version": "2.2.3", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8144,6 +8320,12 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.get": { "version": "4.4.2", "license": "MIT" @@ -8289,6 +8471,12 @@ "node": ">= 12.0.0" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lowercase-keys": { "version": "3.0.0", "dev": true, @@ -9515,6 +9703,21 @@ "@napi-rs/nice": "^1.0.1" } }, + "node_modules/pkcs11js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-2.1.6.tgz", + "integrity": "sha512-+t5jxzB749q8GaEd1yNx3l98xYuaVK6WW/Vjg1Mk1Iy5bMu/A5W4O/9wZGrpOknWF6lFQSb12FXX+eSNxdriwA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/PeculiarVentures" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "dev": true, @@ -9596,7 +9799,9 @@ } }, "node_modules/pm2": { - "version": "6.0.13", + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.14.tgz", + "integrity": "sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==", "dev": true, "license": "AGPL-3.0", "dependencies": { @@ -9604,7 +9809,7 @@ "@pm2/blessed": "0.1.81", "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", - "@pm2/pm2-version-check": "latest", + "@pm2/pm2-version-check": "^1.0.4", "ansis": "4.0.0-node10", "async": "3.2.6", "chokidar": "3.6.0", @@ -9616,7 +9821,7 @@ "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", - "js-yaml": "4.1.0", + "js-yaml": "4.1.1", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "3.0.2", @@ -9919,6 +10124,30 @@ "read": "^1.0.4" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -10139,7 +10368,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11011,6 +11239,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-autogen": { + "version": "2.23.7", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz", + "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", + "license": "MIT", + "dependencies": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.3" + } + }, + "node_modules/swagger-autogen/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/swagger-autogen/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/swagger-autogen/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", + "license": "ISC", + "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/swagger-autogen/node_modules/minimatch": { + "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" + }, + "engines": { + "node": "*" + } + }, "node_modules/swagger-jsdoc": { "version": "6.2.8", "license": "MIT", @@ -11098,6 +11393,8 @@ }, "node_modules/swagger-ui-express": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", "license": "MIT", "dependencies": { "swagger-ui-dist": ">=5.0.0" @@ -11697,7 +11994,6 @@ }, "node_modules/undici-types": { "version": "7.16.0", - "dev": true, "license": "MIT" }, "node_modules/unique-filename": { @@ -12086,7 +12382,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -12110,7 +12405,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12127,7 +12421,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -12135,7 +12428,6 @@ }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12143,12 +12435,10 @@ }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12156,7 +12446,6 @@ }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12169,7 +12458,6 @@ }, "node_modules/yargs/node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" diff --git a/package.json b/package.json index 5e0b0de..fa9ede0 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "schema": "src/prisma/schema.prisma" }, "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "^6.18.0", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", @@ -41,6 +43,7 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "reflect-metadata": "^0.2.2", + "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -60,7 +63,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", - "@types/node": "^24.9.2", + "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/supertest": "^6.0.3", diff --git a/src/app.ts b/src/app.ts index 629b1c3..b1a2910 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,7 +6,6 @@ import express from 'express'; import helmet from 'helmet'; import hpp from 'hpp'; import morgan from 'morgan'; -import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; import { NODE_ENV, PORT, LOG_FORMAT, ORIGIN, CREDENTIALS } from '@config'; import { Routes } from '@interfaces/routes.interface'; @@ -65,26 +64,10 @@ export class App { } private initializeSwagger() { - const options = { - definition: { - openapi: '3.0.0', - info: { - title: 'GP Backend Authentication API', - version: '1.0.0', - description: 'Comprehensive API documentation for authentication routes including email/password auth and Google OAuth', - }, - servers: [ - { - url: `http://localhost:${this.port}`, - description: 'Development server', - }, - ], - }, - apis: ['swagger.yaml'], - }; + const swaggerFile = require('./swagger-output.json'); // Path to the generated swagger file + const swaggerUi = require('swagger-ui-express'); - const specs = swaggerJSDoc(options); - this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); + this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)); } private initializeErrorHandling() { diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts new file mode 100644 index 0000000..514c2ab --- /dev/null +++ b/src/controllers/fabric.controller.ts @@ -0,0 +1,56 @@ +import { NextFunction, Request, Response } from 'express'; +import FabricService from '@/services/fabric.service'; + +class FabricController { + public fabricService = new FabricService(); + + public getAllAssets = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const assets = await this.fabricService.getAllAssets(); + res.status(200).json({ data: assets, message: 'findAll' }); + } catch (error) { + next(error); + } + }; + + public getAssetById = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const assetId = req.params.id; + const asset = await this.fabricService.readAssetByID(assetId); + res.status(200).json({ data: asset, message: 'findOne' }); + } catch (error) { + next(error); + } + }; + + public createAsset = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const { id, color, size, owner, appraisedValue } = req.body; + await this.fabricService.createAsset(id, color, size, owner, appraisedValue); + res.status(201).json({ message: 'created' }); + } catch (error) { + next(error); + } + }; + + public transferAsset = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const assetId = req.params.id; + const { newOwner } = req.body; + const oldOwner = await this.fabricService.transferAsset(assetId, newOwner); + res.status(200).json({ message: `transferred from ${oldOwner} to ${newOwner}` }); + } catch (error) { + next(error); + } + }; + public checkHealth = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + // Simple health check logic + res.status(200).json({ status: 'OK', message: 'Fabric service is healthy' }); + } catch (error) { + next(error); + } + }; +} + +export default FabricController; \ No newline at end of file diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 926ec6a..664e9da 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -18,17 +18,71 @@ export class AuthRoute implements Routes { } private initializeRoutes() { - this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); - this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); - this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); - this.router.post(`${this.path}/refresh`, AuthMiddleware, this.auth.refresh); - this.router.patch(`${this.path}/complete-profile-info`, ValidationMiddleware(CompleteUserProfileDto), AuthMiddleware, this.auth.completeProfile); - this.router.patch(`${this.path}/verify-otp`, AuthMiddleware, this.auth.verifyOTP); - this.router.post(`${this.path}/forget-password`, this.auth.forgetPassword); - this.router.post(`${this.path}/reset-password`, ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword); + this.router.post( + '/auth/signup', + /* #swagger.tags = ['Auth'] */ + ValidationMiddleware(CreateUserDto), + this.auth.signUp, + ); + this.router.post( + '/auth/login', + /* #swagger.tags = ['Auth'] */ + ValidationMiddleware(LoginUserDto), + this.auth.logIn, + ); + this.router.post( + '/auth/logout', + /* #swagger.tags = ['Auth'] */ + AuthMiddleware, + this.auth.logOut, + ); + this.router.post( + '/auth/refresh', + /* #swagger.tags = ['Auth'] */ + AuthMiddleware, + this.auth.refresh, + ); + this.router.patch( + '/auth/complete-profile-info', + /* #swagger.tags = ['Auth'] */ + ValidationMiddleware(CompleteUserProfileDto), + AuthMiddleware, + this.auth.completeProfile, + ); + this.router.patch( + '/auth/verify-otp', + /* #swagger.tags = ['Auth'] */ + AuthMiddleware, + this.auth.verifyOTP, + ); + this.router.post( + '/auth/forget-password', + /* #swagger.tags = ['Auth'] */ + this.auth.forgetPassword, + ); + this.router.post( + '/auth/reset-password', + /* #swagger.tags = ['Auth'] */ + ValidationMiddleware(ResetPasswordDto), + this.auth.resetPassword, + ); - this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); - this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); - this.router.patch(`${this.path}/google/update-phone`, ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber); + this.router.get( + '/auth/google', + /* #swagger.tags = ['Auth'] */ + this.googleAuth.googleOAuth, + ); + this.router.get( + '/auth/google/callback', + /* #swagger.tags = ['Auth'] */ + this.googleAuth.googleOAuthCallback, + ); + this.router.patch( + '/auth/google/update-phone', + /* #swagger.tags = ['Auth'] */ + ValidationMiddleware(UpdateGoogleUserPhoneDto), + AuthMiddleware, + this.googleAuth.updatePhoneNumber, + ); } } diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts new file mode 100644 index 0000000..3d8b24e --- /dev/null +++ b/src/routes/fabric.route.ts @@ -0,0 +1,44 @@ +import { Router } from 'express'; +import FabricContoller from '@/controllers/fabric.controller'; +import { Routes } from '@interfaces/routes.interface'; + +export class FabricRoute implements Routes { + public path = '/assets'; + public router = Router(); + public fabricController = new FabricContoller(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.get( + '/assets', + /* #swagger.tags = ['Fabric'] */ + this.fabricController.getAllAssets, + ); + // Place the explicit health route before the dynamic `:id` route so the literal + // path `/assets/health` is matched first instead of being captured as `:id = 'health'. + this.router.get( + '/assets/health', + /* #swagger.tags = ['Fabric'] */ + this.fabricController.checkHealth, + ); + this.router.get( + '/assets/:id', + /* #swagger.tags = ['Fabric'] */ + this.fabricController.getAssetById, + ); + this.router.post( + '/assets', + /* #swagger.tags = ['Fabric'] */ + this.fabricController.createAsset, + ); + this.router.put( + '/assets/:id/transfer', + /* #swagger.tags = ['Fabric'] */ + this.fabricController.transferAsset, + ); + } +} + diff --git a/src/routes/users.route.ts b/src/routes/users.route.ts index b750b9f..648f88a 100644 --- a/src/routes/users.route.ts +++ b/src/routes/users.route.ts @@ -14,10 +14,32 @@ export class UserRoute implements Routes { } private initializeRoutes() { - this.router.get(`${this.path}`, this.user.getUsers); - this.router.get(`${this.path}/:id(\\d+)`, this.user.getUserById); - this.router.post(`${this.path}`, ValidationMiddleware(CreateUserDto), this.user.createUser); - this.router.put(`${this.path}/:id(\\d+)`, ValidationMiddleware(CreateUserDto, true), this.user.updateUser); - this.router.delete(`${this.path}/:id(\\d+)`, this.user.deleteUser); + this.router.get( + '/users', + /* #swagger.tags = ['Users'] */ + this.user.getUsers, + ); + this.router.get( + '/users/:id(\\d+)', + /* #swagger.tags = ['Users'] */ + this.user.getUserById, + ); + this.router.post( + '/users', + /* #swagger.tags = ['Users'] */ + ValidationMiddleware(CreateUserDto), + this.user.createUser, + ); + this.router.put( + '/users/:id(\\d+)', + /* #swagger.tags = ['Users'] */ + ValidationMiddleware(CreateUserDto, true), + this.user.updateUser, + ); + this.router.delete( + '/users/:id(\\d+)', + /* #swagger.tags = ['Users'] */ + this.user.deleteUser, + ); } } diff --git a/src/server.ts b/src/server.ts index 6422afb..2a10f95 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,9 @@ import { App } from '@/app'; import { AuthRoute } from '@routes/auth.route'; import { ValidateEnv } from '@utils/validateEnv'; - +import { FabricRoute } from '@routes/fabric.route'; ValidateEnv(); -const app = new App([new AuthRoute()]); +const app = new App([new AuthRoute(), new FabricRoute()]); app.listen(); diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts new file mode 100644 index 0000000..313829c --- /dev/null +++ b/src/services/fabric.service.ts @@ -0,0 +1,129 @@ +import * as grpc from '@grpc/grpc-js'; +import { connect, Contract, Gateway, Identity, Signer, signers } from '@hyperledger/fabric-gateway'; +import * as crypto from 'crypto'; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { TextDecoder } from 'util'; +import { HttpException } from '@/exceptions/HttpException'; + +class FabricService { + private gateway: Gateway | undefined; + private contract: Contract | undefined; + private readonly utf8Decoder = new TextDecoder(); + + // Configuration - should be moved to your config/index.ts and .env file + private readonly channelName = process.env.CHANNEL_NAME || 'mychannel'; + private readonly chaincodeName = process.env.CHAINCODE_NAME || 'test'; + private readonly mspId = process.env.MSP_ID || 'Org1MSP'; + private readonly cryptoPath = process.env.CRYPTO_PATH || path.resolve(__dirname, '../../../Blockchain/test-network/organizations/peerOrganizations/org1.example.com'); + private readonly keyDirectoryPath = process.env.KEY_DIRECTORY_PATH || path.resolve(this.cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'); + private readonly certDirectoryPath = process.env.CERT_DIRECTORY_PATH || path.resolve(this.cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts'); + private readonly tlsCertPath = process.env.TLS_CERT_PATH || path.resolve(this.cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); + private readonly peerEndpoint = process.env.PEER_ENDPOINT || 'localhost:7051'; + private readonly peerHostAlias = process.env.PEER_HOST_ALIAS || 'peer0.org1.example.com'; + private client: grpc.Client | undefined; + + constructor() { + this.connectToNetwork().catch(error => { + console.error('Failed to connect to Fabric network on initialization:', error); + process.exit(1); + }); + } + + private async connectToNetwork(): Promise { + try { + this.client = await this.newGrpcConnection(); + this.gateway = connect({ + client: this.client, + identity: await this.newIdentity(), + signer: await this.newSigner(), + }); + const network = this.gateway.getNetwork(this.channelName); + this.contract = network.getContract(this.chaincodeName); + await this.initLedger(); + console.log('*** Fabric Service Initialized and Ledger Ready ***'); + } catch (error) { + console.error('fabric network not connected'); + // Do not throw to avoid crashing the application during startup. + // Leave gateway/client/contract undefined so callers can detect uninitialized service. + this.gateway = undefined; + this.client = undefined; + this.contract = undefined; + return; + } + } + + public async getAllAssets(): Promise { + console.log('\n--> Evaluate Transaction: GetAllAssets'); + const resultBytes = await this.contract.evaluateTransaction('GetAllAssets'); + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson); + } + + public async createAsset(id: string, color: string, size: string, owner: string, appraisedValue: string): Promise { + console.log('\n--> Submit Transaction: CreateAsset'); + await this.contract.submitTransaction('CreateAsset', id, color, size, owner, appraisedValue); + } + + public async readAssetByID(assetId: string): Promise { + console.log('\n--> Evaluate Transaction: ReadAsset'); + const resultBytes = await this.contract.evaluateTransaction('ReadAsset', assetId); + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson); + } + + public async transferAsset(assetId: string, newOwner: string): Promise { + console.log('\n--> Async Submit Transaction: TransferAsset'); + const commit = await this.contract.submitAsync('TransferAsset', { + arguments: [assetId, newOwner], + }); + const oldOwner = this.utf8Decoder.decode(commit.getResult()); + const status = await commit.getStatus(); + if (!status.successful) { + throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${String(status.code)}`); + } + return oldOwner; + } + + private async initLedger(): Promise { + console.log('\n--> Submit Transaction: InitLedger'); + await this.contract.submitTransaction('InitLedger'); + console.log('*** InitLedger transaction committed successfully'); + } + + private async newGrpcConnection(): Promise { + const tlsRootCert = await fs.readFile(this.tlsCertPath); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + return new grpc.Client(this.peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': this.peerHostAlias, + }); + } + + private async newIdentity(): Promise { + const certPath = await this.getFirstDirFileName(this.certDirectoryPath); + const credentials = await fs.readFile(certPath); + return { mspId: this.mspId, credentials }; + } + + private async newSigner(): Promise { + const keyPath = await this.getFirstDirFileName(this.keyDirectoryPath); + const privateKeyPem = await fs.readFile(keyPath); + const privateKey = crypto.createPrivateKey(privateKeyPem); + return signers.newPrivateKeySigner(privateKey); + } + + private async getFirstDirFileName(dirPath: string): Promise { + const files = await fs.readdir(dirPath); + if (!files[0]) { + throw new Error(`No files in directory: ${dirPath}`); + } + return path.join(dirPath, files[0]); + } + + public close(): void { + this.gateway?.close(); + this.client?.close(); + } +} + +export default FabricService; \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json new file mode 100644 index 0000000..3310160 --- /dev/null +++ b/src/swagger-output.json @@ -0,0 +1,334 @@ +{ + "swagger": "2.0", + "info": { + "title": "My API", + "description": "Description", + "version": "1.0.0" + }, + "host": "localhost:3000", + "basePath": "/", + "tags": [ + { + "name": "Auth", + "description": "Authentication and account endpoints" + }, + { + "name": "Users", + "description": "User management endpoints" + }, + { + "name": "Fabric", + "description": "Hyperledger Fabric asset endpoints" + } + ], + "schemes": [ + "http" + ], + "paths": { + "/auth/signup": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/login": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/logout": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/complete-profile-info": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/verify-otp": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/forget-password": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/reset-password": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/google": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/google/callback": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/auth/google/update-phone": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/assets": { + "get": { + "tags": [ + "Fabric" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + }, + "post": { + "tags": [ + "Fabric" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/assets/health": { + "get": { + "tags": [ + "Fabric" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/assets/{id}": { + "get": { + "tags": [ + "Fabric" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/assets/{id}/transfer": { + "put": { + "tags": [ + "Fabric" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/users": { + "get": { + "tags": [ + "Users" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + }, + "post": { + "tags": [ + "Users" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/users/{id(\\\\d+)}": { + "get": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "id(\\\\d+)", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + }, + "put": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "id(\\\\d+)", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + }, + "delete": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "id(\\\\d+)", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js new file mode 100644 index 0000000..1acc604 --- /dev/null +++ b/src/swagger.js @@ -0,0 +1,20 @@ +import swaggerAutogen from 'swagger-autogen'; + +const doc = { + info: { + title: 'My API', + description: 'Description', + }, + host: 'localhost:3000', + schemes: ['http'], + tags: [ + { name: 'Auth', description: 'Authentication and account endpoints' }, + { name: 'Users', description: 'User management endpoints' }, + { name: 'Fabric', description: 'Hyperledger Fabric asset endpoints' }, + ], +}; + +const outputFile = './swagger-output.json'; +const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/users.route.ts']; + +swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/swagger-output.json b/swagger-output.json new file mode 100644 index 0000000..e69de29 diff --git a/swagger.yaml b/swagger.yaml index 5562f8b..3e25cd1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -18,8 +18,63 @@ tags: description: Email/Password authentication endpoints - name: Google OAuth description: Google OAuth authentication endpoints + - name: Fabric + description: Hyperledger Fabric asset management endpoints paths: + /assets: + get: + tags: + - Fabric + summary: Get all assets + description: Retrieve a list of all assets from the blockchain ledger. + responses: + '200': + description: A list of assets. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Asset' + message: + type: string + example: findAll + '500': + description: Internal Server Error + /assets/{id}: + get: + tags: + - Fabric + summary: Get asset by ID + description: Retrieve a specific asset from the blockchain ledger by its ID. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the asset to retrieve. + responses: + '200': + description: The requested asset. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Asset' + message: + type: string + example: findOne + '404': + description: Asset not found + '500': + description: Internal Server Error /auth/signup: post: tags: @@ -554,6 +609,25 @@ components: maxLength: 15 example: '+1234567890' + Asset: + type: object + properties: + ID: + type: string + description: The unique identifier of the asset. + Color: + type: string + description: The color of the asset. + Size: + type: string + description: The size of the asset. + Owner: + type: string + description: The current owner of the asset. + AppraisedValue: + type: string + description: The appraised value of the asset. + User: type: object properties: From 2e0d9cd1753eb2bd9dd4498174e17a9cda9b02dc Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 29 Nov 2025 23:27:49 +0200 Subject: [PATCH 032/210] refactor: remove user-related routes and services; update swagger tags for consistency --- src/controllers/users.controller.ts | 63 ------------- src/routes/auth.route.ts | 106 ++++++++++++++++++--- src/routes/fabric.route.ts | 10 +- src/routes/users.route.ts | 45 --------- src/services/users.service.ts | 49 ---------- src/swagger-output.json | 137 ++++++++-------------------- src/swagger.js | 7 +- 7 files changed, 137 insertions(+), 280 deletions(-) delete mode 100644 src/controllers/users.controller.ts delete mode 100644 src/routes/users.route.ts delete mode 100644 src/services/users.service.ts diff --git a/src/controllers/users.controller.ts b/src/controllers/users.controller.ts deleted file mode 100644 index a3b5d4d..0000000 --- a/src/controllers/users.controller.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { NextFunction, Request, Response } from 'express'; -import { Container } from 'typedi'; -import { User } from '@interfaces/users.interface'; -import { UserService } from '@services/users.service'; - -export class UserController { - public user = Container.get(UserService); - - public getUsers = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const findAllUsersData: User[] = await this.user.findAllUser(); - - res.status(200).json({ data: findAllUsersData, message: 'findAll' }); - } catch (error) { - next(error); - } - }; - - public getUserById = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userId = Number(req.params.id); - const findOneUserData: User = await this.user.findUserById(userId); - - res.status(200).json({ data: findOneUserData, message: 'findOne' }); - } catch (error) { - next(error); - } - }; - - public createUser = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userData: User = req.body; - const createUserData: User = await this.user.createUser(userData); - - res.status(201).json({ data: createUserData, message: 'created' }); - } catch (error) { - next(error); - } - }; - - public updateUser = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userId = Number(req.params.id); - const userData: User = req.body; - const updateUserData: User = await this.user.updateUser(userId, userData); - - res.status(200).json({ data: updateUserData, message: 'updated' }); - } catch (error) { - next(error); - } - }; - - public deleteUser = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userId = Number(req.params.id); - const deleteUserData: User = await this.user.deleteUser(userId); - - res.status(200).json({ data: deleteUserData, message: 'deleted' }); - } catch (error) { - next(error); - } - }; -} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 5d714eb..ce74ac2 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -4,7 +4,8 @@ import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { GoogleAuthController } from '@/controllers/googleAuth.controller'; -import { ValidationMiddleware } from '@middlewares/validation.middleware'; +import +/* #swagger.tags = ['auth'] */ { ValidationMiddleware } from '@middlewares/validation.middleware'; import { UpdateGoogleUserPhoneDto } from '@/dtos/googleUsers.dto'; export class AuthRoute implements Routes { @@ -18,19 +19,94 @@ export class AuthRoute implements Routes { } private initializeRoutes() { - this.router.post(`${this.path}/signup`, ValidationMiddleware(CreateUserDto), this.auth.signUp); - this.router.post(`${this.path}/login`, ValidationMiddleware(LoginUserDto), this.auth.logIn); - this.router.post(`${this.path}/logout`, AuthMiddleware, this.auth.logOut); - this.router.post(`${this.path}/refresh`, AuthMiddleware, this.auth.refresh); - this.router.patch(`${this.path}/complete-profile-info`, ValidationMiddleware(CompleteUserProfileDto), AuthMiddleware, this.auth.completeProfile); - this.router.patch(`${this.path}/verify-otp`, AuthMiddleware, this.auth.verifyOTP); - this.router.post(`${this.path}/forget-password`, this.auth.forgetPassword); - this.router.post(`${this.path}/reset-password`, ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword); - this.router.post(`${this.path}/resend-otp`, AuthMiddleware, this.auth.resendOTP); - - this.router.get(`${this.path}/google`, this.googleAuth.googleOAuth); - this.router.get(`${this.path}/google/callback`, this.googleAuth.googleOAuthCallback); - this.router.patch(`${this.path}/google/update-phone`, ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber); - this.router.get(`${this.path}/google/userData`, AuthMiddleware, this.googleAuth.getGoogleUserData); + this.router.post( + `/auth/signup`, + /* #swagger.tags = ['auth'] */ + ValidationMiddleware(CreateUserDto), + this.auth.signUp, + ); + + this.router.post( + `/auth/login`, + /* #swagger.tags = ['auth'] */ + ValidationMiddleware(LoginUserDto), + this.auth.logIn, + ); + + this.router.post( + `/auth/logout`, + /* #swagger.tags = ['auth'] */ + AuthMiddleware, + this.auth.logOut, + ); + + this.router.post( + `/auth/refresh`, + /* #swagger.tags = ['auth'] */ + AuthMiddleware, + this.auth.refresh, + ); + + this.router.patch( + `/auth/complete-profile-info`, + /* #swagger.tags = ['auth'] */ + ValidationMiddleware(CompleteUserProfileDto), + AuthMiddleware, + this.auth.completeProfile, + ); + + this.router.patch( + `/auth/verify-otp`, + /* #swagger.tags = ['auth'] */ + AuthMiddleware, + this.auth.verifyOTP, + ); + + this.router.post( + `/auth/forget-password`, + /* #swagger.tags = ['auth'] */ + this.auth.forgetPassword, + ); + + this.router.post( + `/auth/reset-password`, + /* #swagger.tags = ['auth'] */ + ValidationMiddleware(ResetPasswordDto), + this.auth.resetPassword, + ); + + this.router.post( + `/auth/resend-otp`, + /* #swagger.tags = ['auth'] */ + AuthMiddleware, + this.auth.resendOTP, + ); + + this.router.get( + `/auth/google`, + /* #swagger.tags = ['auth'] */ + this.googleAuth.googleOAuth, + ); + + this.router.get( + `/auth/google/callback`, + /* #swagger.tags = ['auth'] */ + this.googleAuth.googleOAuthCallback, + ); + + this.router.patch( + `/auth/google/update-phone`, + /* #swagger.tags = ['auth'] */ + ValidationMiddleware(UpdateGoogleUserPhoneDto), + AuthMiddleware, + this.googleAuth.updatePhoneNumber, + ); + + this.router.get( + `/auth/google/userData`, + /* #swagger.tags = ['auth'] */ + AuthMiddleware, + this.googleAuth.getGoogleUserData, + ); } } diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index 3d8b24e..ead84b9 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -14,29 +14,29 @@ export class FabricRoute implements Routes { private initializeRoutes() { this.router.get( '/assets', - /* #swagger.tags = ['Fabric'] */ + /* #swagger.tags = ['fabric'] */ this.fabricController.getAllAssets, ); // Place the explicit health route before the dynamic `:id` route so the literal // path `/assets/health` is matched first instead of being captured as `:id = 'health'. this.router.get( '/assets/health', - /* #swagger.tags = ['Fabric'] */ + /* #swagger.tags = ['fabric'] */ this.fabricController.checkHealth, ); this.router.get( '/assets/:id', - /* #swagger.tags = ['Fabric'] */ + /* #swagger.tags = ['fabric'] */ this.fabricController.getAssetById, ); this.router.post( '/assets', - /* #swagger.tags = ['Fabric'] */ + /* #swagger.tags = ['fabric'] */ this.fabricController.createAsset, ); this.router.put( '/assets/:id/transfer', - /* #swagger.tags = ['Fabric'] */ + /* #swagger.tags = ['fabric'] */ this.fabricController.transferAsset, ); } diff --git a/src/routes/users.route.ts b/src/routes/users.route.ts deleted file mode 100644 index 648f88a..0000000 --- a/src/routes/users.route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Router } from 'express'; -import { UserController } from '@controllers/users.controller'; -import { CreateUserDto } from '@dtos/users.dto'; -import { Routes } from '@interfaces/routes.interface'; -import { ValidationMiddleware } from '@middlewares/validation.middleware'; - -export class UserRoute implements Routes { - public path = '/users'; - public router = Router(); - public user = new UserController(); - - constructor() { - this.initializeRoutes(); - } - - private initializeRoutes() { - this.router.get( - '/users', - /* #swagger.tags = ['Users'] */ - this.user.getUsers, - ); - this.router.get( - '/users/:id(\\d+)', - /* #swagger.tags = ['Users'] */ - this.user.getUserById, - ); - this.router.post( - '/users', - /* #swagger.tags = ['Users'] */ - ValidationMiddleware(CreateUserDto), - this.user.createUser, - ); - this.router.put( - '/users/:id(\\d+)', - /* #swagger.tags = ['Users'] */ - ValidationMiddleware(CreateUserDto, true), - this.user.updateUser, - ); - this.router.delete( - '/users/:id(\\d+)', - /* #swagger.tags = ['Users'] */ - this.user.deleteUser, - ); - } -} diff --git a/src/services/users.service.ts b/src/services/users.service.ts deleted file mode 100644 index 015e530..0000000 --- a/src/services/users.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { PrismaClient } from '@prisma/client'; -import { hash } from 'bcrypt'; -import { Service } from 'typedi'; -import { CreateUserDto } from '@dtos/users.dto'; -import { HttpException } from '@/exceptions/HttpException'; -import { User } from '@interfaces/users.interface'; - -@Service() -export class UserService { - public user = new PrismaClient().user; - - public async findAllUser(): Promise { - const allUser: User[] = await this.user.findMany(); - return allUser; - } - - public async findUserById(userId: number): Promise { - const findUser: User = await this.user.findUnique({ where: { id: userId } }); - if (!findUser) throw new HttpException(409, "User doesn't exist"); - - return findUser; - } - - public async createUser(userData: CreateUserDto): Promise { - const findUser: User = await this.user.findUnique({ where: { email: userData.email } }); - if (findUser) throw new HttpException(409, `This email ${userData.email} already exists`); - - const hashedPassword = await hash(userData.password, 10); - const createUserData: User = await this.user.create({ data: { ...userData, password: hashedPassword } }); - return createUserData; - } - - public async updateUser(userId: number, userData: CreateUserDto): Promise { - const findUser: User = await this.user.findUnique({ where: { id: userId } }); - if (!findUser) throw new HttpException(409, "User doesn't exist"); - - const hashedPassword = await hash(userData.password, 10); - const updateUserData = await this.user.update({ where: { id: userId }, data: { ...userData, password: hashedPassword } }); - return updateUserData; - } - - public async deleteUser(userId: number): Promise { - const findUser: User = await this.user.findUnique({ where: { id: userId } }); - if (!findUser) throw new HttpException(409, "User doesn't exist"); - - const deleteUserData = await this.user.delete({ where: { id: userId } }); - return deleteUserData; - } -} diff --git a/src/swagger-output.json b/src/swagger-output.json index 3310160..95fddd8 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -9,15 +9,11 @@ "basePath": "/", "tags": [ { - "name": "Auth", + "name": "auth", "description": "Authentication and account endpoints" }, { - "name": "Users", - "description": "User management endpoints" - }, - { - "name": "Fabric", + "name": "fabric", "description": "Hyperledger Fabric asset endpoints" } ], @@ -28,7 +24,7 @@ "/auth/signup": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -41,7 +37,7 @@ "/auth/login": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -54,7 +50,7 @@ "/auth/logout": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -67,7 +63,7 @@ "/auth/refresh": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -80,7 +76,7 @@ "/auth/complete-profile-info": { "patch": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -93,7 +89,7 @@ "/auth/verify-otp": { "patch": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -106,7 +102,7 @@ "/auth/forget-password": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -119,7 +115,7 @@ "/auth/reset-password": { "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -129,10 +125,10 @@ } } }, - "/auth/google": { - "get": { + "/auth/resend-otp": { + "post": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -142,10 +138,10 @@ } } }, - "/auth/google/callback": { + "/auth/google": { "get": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -155,10 +151,10 @@ } } }, - "/auth/google/update-phone": { - "patch": { + "/auth/google/callback": { + "get": { "tags": [ - "Auth" + "auth" ], "description": "", "responses": { @@ -168,21 +164,10 @@ } } }, - "/assets": { - "get": { - "tags": [ - "Fabric" - ], - "description": "", - "responses": { - "default": { - "description": "" - } - } - }, - "post": { + "/auth/google/update-phone": { + "patch": { "tags": [ - "Fabric" + "auth" ], "description": "", "responses": { @@ -192,10 +177,10 @@ } } }, - "/assets/health": { + "/auth/google/userData": { "get": { "tags": [ - "Fabric" + "auth" ], "description": "", "responses": { @@ -205,41 +190,23 @@ } } }, - "/assets/{id}": { + "/assets": { "get": { "tags": [ - "Fabric" + "fabric" ], "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], "responses": { "default": { "description": "" } } - } - }, - "/assets/{id}/transfer": { - "put": { + }, + "post": { "tags": [ - "Fabric" + "fabric" ], "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], "responses": { "default": { "description": "" @@ -247,21 +214,10 @@ } } }, - "/users": { + "/assets/health": { "get": { "tags": [ - "Users" - ], - "description": "", - "responses": { - "default": { - "description": "" - } - } - }, - "post": { - "tags": [ - "Users" + "fabric" ], "description": "", "responses": { @@ -271,15 +227,15 @@ } } }, - "/users/{id(\\\\d+)}": { + "/assets/{id}": { "get": { "tags": [ - "Users" + "fabric" ], "description": "", "parameters": [ { - "name": "id(\\\\d+)", + "name": "id", "in": "path", "required": true, "type": "string" @@ -290,34 +246,17 @@ "description": "" } } - }, + } + }, + "/assets/{id}/transfer": { "put": { "tags": [ - "Users" + "fabric" ], "description": "", "parameters": [ { - "name": "id(\\\\d+)", - "in": "path", - "required": true, - "type": "string" - } - ], - "responses": { - "default": { - "description": "" - } - } - }, - "delete": { - "tags": [ - "Users" - ], - "description": "", - "parameters": [ - { - "name": "id(\\\\d+)", + "name": "id", "in": "path", "required": true, "type": "string" diff --git a/src/swagger.js b/src/swagger.js index 1acc604..0b77177 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -8,13 +8,12 @@ const doc = { host: 'localhost:3000', schemes: ['http'], tags: [ - { name: 'Auth', description: 'Authentication and account endpoints' }, - { name: 'Users', description: 'User management endpoints' }, - { name: 'Fabric', description: 'Hyperledger Fabric asset endpoints' }, + { name: 'auth', description: 'Authentication and account endpoints' }, + { name: 'fabric', description: 'Hyperledger Fabric asset endpoints' }, ], }; const outputFile = './swagger-output.json'; -const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/users.route.ts']; +const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From b84c19144698fbd9e4eb3ba93ef3dadd2c76f9d4 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 29 Nov 2025 23:27:59 +0200 Subject: [PATCH 033/210] feat: implement medical records management; update routes, services, and Swagger documentation --- src/controllers/fabric.controller.ts | 28 ++++---- src/dtos/medicalRecord.dto.ts | 63 ++++++++++++++++++ src/interfaces/index.ts | 3 + src/interfaces/medical-records.interface.ts | 10 +++ src/routes/auth.route.ts | 30 ++++----- src/routes/fabric.route.ts | 38 ++++++----- src/services/fabric.service.ts | 71 ++++++++++++++------- src/swagger-output.json | 56 ++++++++-------- src/swagger.js | 4 +- 9 files changed, 202 insertions(+), 101 deletions(-) create mode 100644 src/dtos/medicalRecord.dto.ts create mode 100644 src/interfaces/medical-records.interface.ts diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts index 514c2ab..69de8d7 100644 --- a/src/controllers/fabric.controller.ts +++ b/src/controllers/fabric.controller.ts @@ -4,41 +4,39 @@ import FabricService from '@/services/fabric.service'; class FabricController { public fabricService = new FabricService(); - public getAllAssets = async (req: Request, res: Response, next: NextFunction): Promise => { + public getAllRecords = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const assets = await this.fabricService.getAllAssets(); - res.status(200).json({ data: assets, message: 'findAll' }); + const records = await this.fabricService.getAllRecords(); + res.status(200).json({ data: records, message: 'findAll' }); } catch (error) { next(error); } }; - public getAssetById = async (req: Request, res: Response, next: NextFunction): Promise => { + public getRecordById = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const assetId = req.params.id; - const asset = await this.fabricService.readAssetByID(assetId); - res.status(200).json({ data: asset, message: 'findOne' }); + const patientId = req.params.patientId; + const record = await this.fabricService.getRecordByPatientId(patientId); + res.status(200).json({ data: record, message: 'findOne' }); } catch (error) { next(error); } }; - public createAsset = async (req: Request, res: Response, next: NextFunction): Promise => { + public addRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const { id, color, size, owner, appraisedValue } = req.body; - await this.fabricService.createAsset(id, color, size, owner, appraisedValue); + await this.fabricService.addRecord(req.body); res.status(201).json({ message: 'created' }); } catch (error) { next(error); } }; - public transferAsset = async (req: Request, res: Response, next: NextFunction): Promise => { + public updateRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const assetId = req.params.id; - const { newOwner } = req.body; - const oldOwner = await this.fabricService.transferAsset(assetId, newOwner); - res.status(200).json({ message: `transferred from ${oldOwner} to ${newOwner}` }); + const patientId = req.params.patientId; + await this.fabricService.updateRecord(patientId, req.body); + res.status(200).json({ message: 'updated' }); } catch (error) { next(error); } diff --git a/src/dtos/medicalRecord.dto.ts b/src/dtos/medicalRecord.dto.ts new file mode 100644 index 0000000..10fef82 --- /dev/null +++ b/src/dtos/medicalRecord.dto.ts @@ -0,0 +1,63 @@ +import { IsDateString, IsOptional, IsString, Length } from 'class-validator'; + +export class CreateMedicalRecordDto { + @IsString() + @Length(3, 64) + public patientId: string; + + @IsString() + @Length(1, 64) + public firstName: string; + + @IsString() + @Length(1, 64) + public lastName: string; + + @IsDateString() + public dateOfBirth: string; + + @IsString() + @Length(1, 32) + public gender: string; + + @IsString() + @Length(1, 8) + public bloodType: string; + + @IsString() + @Length(10, 128) + public ipfsCid: string; + + @IsOptional() + @IsString() + public summary?: string; +} + +export class UpdateMedicalRecordDto { + @IsString() + @Length(1, 64) + public firstName: string; + + @IsString() + @Length(1, 64) + public lastName: string; + + @IsDateString() + public dateOfBirth: string; + + @IsString() + @Length(1, 32) + public gender: string; + + @IsString() + @Length(1, 8) + public bloodType: string; + + @IsString() + @Length(10, 128) + public ipfsCid: string; + + @IsOptional() + @IsString() + public summary?: string; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 9c4853b..8f9c26b 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -24,3 +24,6 @@ export * from './clinics.interface'; // Audit Logs export * from './audit-logs.interface'; + +// Medical Records +export * from './medical-records.interface'; diff --git a/src/interfaces/medical-records.interface.ts b/src/interfaces/medical-records.interface.ts new file mode 100644 index 0000000..f18d4f3 --- /dev/null +++ b/src/interfaces/medical-records.interface.ts @@ -0,0 +1,10 @@ +export interface MedicalRecord { + patientId: string; + firstName: string; + lastName: string; + dateOfBirth: string; + gender: string; + bloodType: string; + ipfsCid: string; + summary?: string; +} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ce74ac2..ef2cf19 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -5,9 +5,9 @@ import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { GoogleAuthController } from '@/controllers/googleAuth.controller'; import -/* #swagger.tags = ['auth'] */ { ValidationMiddleware } from '@middlewares/validation.middleware'; +/* #swagger.tags = ['Auth'] */ { ValidationMiddleware } from '@middlewares/validation.middleware'; import { UpdateGoogleUserPhoneDto } from '@/dtos/googleUsers.dto'; - +https://github.com/Blockchain-Based-EMR-System/Backendhttps://github.com/Blockchain-Based-EMR-System/Backend export class AuthRoute implements Routes { public path = '/auth'; public router = Router(); @@ -21,35 +21,35 @@ export class AuthRoute implements Routes { private initializeRoutes() { this.router.post( `/auth/signup`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ ValidationMiddleware(CreateUserDto), this.auth.signUp, ); this.router.post( `/auth/login`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ ValidationMiddleware(LoginUserDto), this.auth.logIn, ); this.router.post( `/auth/logout`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ AuthMiddleware, this.auth.logOut, ); this.router.post( `/auth/refresh`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ AuthMiddleware, this.auth.refresh, ); this.router.patch( `/auth/complete-profile-info`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ ValidationMiddleware(CompleteUserProfileDto), AuthMiddleware, this.auth.completeProfile, @@ -57,46 +57,46 @@ export class AuthRoute implements Routes { this.router.patch( `/auth/verify-otp`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ AuthMiddleware, this.auth.verifyOTP, ); this.router.post( `/auth/forget-password`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ this.auth.forgetPassword, ); this.router.post( `/auth/reset-password`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword, ); this.router.post( `/auth/resend-otp`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ AuthMiddleware, this.auth.resendOTP, ); this.router.get( `/auth/google`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ this.googleAuth.googleOAuth, ); this.router.get( `/auth/google/callback`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ this.googleAuth.googleOAuthCallback, ); this.router.patch( `/auth/google/update-phone`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber, @@ -104,7 +104,7 @@ export class AuthRoute implements Routes { this.router.get( `/auth/google/userData`, - /* #swagger.tags = ['auth'] */ + /* #swagger.tags = ['Auth'] */ AuthMiddleware, this.googleAuth.getGoogleUserData, ); diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index ead84b9..08b360a 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -1,9 +1,11 @@ import { Router } from 'express'; import FabricContoller from '@/controllers/fabric.controller'; +import { CreateMedicalRecordDto, UpdateMedicalRecordDto } from '@/dtos/medicalRecord.dto'; +import { ValidationMiddleware } from '@middlewares/validation.middleware'; import { Routes } from '@interfaces/routes.interface'; export class FabricRoute implements Routes { - public path = '/assets'; + public path = '/records'; public router = Router(); public fabricController = new FabricContoller(); @@ -13,31 +15,33 @@ export class FabricRoute implements Routes { private initializeRoutes() { this.router.get( - '/assets', - /* #swagger.tags = ['fabric'] */ - this.fabricController.getAllAssets, + '/records', + /* #swagger.tags = ['MedicalRecords'] */ + this.fabricController.getAllRecords, ); - // Place the explicit health route before the dynamic `:id` route so the literal - // path `/assets/health` is matched first instead of being captured as `:id = 'health'. + // Place the explicit health route before the dynamic `:patientId` route so the literal + // path `/records/health` is matched first instead of being captured as `:patientId = 'health'. this.router.get( - '/assets/health', - /* #swagger.tags = ['fabric'] */ + '/records/health', + /* #swagger.tags = ['MedicalRecords'] */ this.fabricController.checkHealth, ); this.router.get( - '/assets/:id', - /* #swagger.tags = ['fabric'] */ - this.fabricController.getAssetById, + '/records/:patientId', + /* #swagger.tags = ['MedicalRecords'] */ + this.fabricController.getRecordById, ); this.router.post( - '/assets', - /* #swagger.tags = ['fabric'] */ - this.fabricController.createAsset, + '/records', + /* #swagger.tags = ['MedicalRecords'] */ + ValidationMiddleware(CreateMedicalRecordDto), + this.fabricController.addRecord, ); this.router.put( - '/assets/:id/transfer', - /* #swagger.tags = ['fabric'] */ - this.fabricController.transferAsset, + '/records/:patientId', + /* #swagger.tags = ['MedicalRecords'] */ + ValidationMiddleware(UpdateMedicalRecordDto), + this.fabricController.updateRecord, ); } } diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index 313829c..dd920f7 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -5,6 +5,7 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import { TextDecoder } from 'util'; import { HttpException } from '@/exceptions/HttpException'; +import { MedicalRecord } from '@/interfaces/medical-records.interface'; class FabricService { private gateway: Gateway | undefined; @@ -53,44 +54,68 @@ class FabricService { } } - public async getAllAssets(): Promise { - console.log('\n--> Evaluate Transaction: GetAllAssets'); - const resultBytes = await this.contract.evaluateTransaction('GetAllAssets'); + public async getAllRecords(): Promise { + const contract = this.ensureContract(); + console.log('\n--> Evaluate Transaction: GetAllRecords'); + const resultBytes = await contract.evaluateTransaction('GetAllRecords'); const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson); + return JSON.parse(resultJson) as MedicalRecord[]; } - public async createAsset(id: string, color: string, size: string, owner: string, appraisedValue: string): Promise { - console.log('\n--> Submit Transaction: CreateAsset'); - await this.contract.submitTransaction('CreateAsset', id, color, size, owner, appraisedValue); + public async addRecord(payload: MedicalRecord): Promise { + const contract = this.ensureContract(); + console.log('\n--> Submit Transaction: AddRecord'); + await contract.submitTransaction( + 'AddRecord', + payload.patientId, + payload.firstName, + payload.lastName, + payload.dateOfBirth, + payload.gender, + payload.bloodType, + payload.ipfsCid, + payload.summary || '', + ); } - public async readAssetByID(assetId: string): Promise { - console.log('\n--> Evaluate Transaction: ReadAsset'); - const resultBytes = await this.contract.evaluateTransaction('ReadAsset', assetId); + public async getRecordByPatientId(patientId: string): Promise { + const contract = this.ensureContract(); + console.log('\n--> Evaluate Transaction: GetRecord'); + const resultBytes = await contract.evaluateTransaction('GetRecord', patientId); const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson); + return JSON.parse(resultJson) as MedicalRecord; } - public async transferAsset(assetId: string, newOwner: string): Promise { - console.log('\n--> Async Submit Transaction: TransferAsset'); - const commit = await this.contract.submitAsync('TransferAsset', { - arguments: [assetId, newOwner], - }); - const oldOwner = this.utf8Decoder.decode(commit.getResult()); - const status = await commit.getStatus(); - if (!status.successful) { - throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${String(status.code)}`); - } - return oldOwner; + public async updateRecord(patientId: string, payload: Omit): Promise { + const contract = this.ensureContract(); + console.log('\n--> Submit Transaction: UpdateRecord'); + await contract.submitTransaction( + 'UpdateRecord', + patientId, + payload.firstName, + payload.lastName, + payload.dateOfBirth, + payload.gender, + payload.bloodType, + payload.ipfsCid, + payload.summary || '', + ); } private async initLedger(): Promise { + const contract = this.ensureContract(); console.log('\n--> Submit Transaction: InitLedger'); - await this.contract.submitTransaction('InitLedger'); + await contract.submitTransaction('InitLedger'); console.log('*** InitLedger transaction committed successfully'); } + private ensureContract(): Contract { + if (!this.contract) { + throw new HttpException(503, 'Fabric network connection is not ready'); + } + return this.contract; + } + private async newGrpcConnection(): Promise { const tlsRootCert = await fs.readFile(this.tlsCertPath); const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); diff --git a/src/swagger-output.json b/src/swagger-output.json index 95fddd8..ff85374 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -9,12 +9,12 @@ "basePath": "/", "tags": [ { - "name": "auth", + "name": "Auth", "description": "Authentication and account endpoints" }, { - "name": "fabric", - "description": "Hyperledger Fabric asset endpoints" + "name": "MedicalRecords", + "description": "Hyperledger Fabric medical record endpoints" } ], "schemes": [ @@ -24,7 +24,7 @@ "/auth/signup": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -37,7 +37,7 @@ "/auth/login": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -50,7 +50,7 @@ "/auth/logout": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -63,7 +63,7 @@ "/auth/refresh": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -76,7 +76,7 @@ "/auth/complete-profile-info": { "patch": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -89,7 +89,7 @@ "/auth/verify-otp": { "patch": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -102,7 +102,7 @@ "/auth/forget-password": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -115,7 +115,7 @@ "/auth/reset-password": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -128,7 +128,7 @@ "/auth/resend-otp": { "post": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -141,7 +141,7 @@ "/auth/google": { "get": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -154,7 +154,7 @@ "/auth/google/callback": { "get": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -167,7 +167,7 @@ "/auth/google/update-phone": { "patch": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -180,7 +180,7 @@ "/auth/google/userData": { "get": { "tags": [ - "auth" + "Auth" ], "description": "", "responses": { @@ -190,10 +190,10 @@ } } }, - "/assets": { + "/records": { "get": { "tags": [ - "fabric" + "MedicalRecords" ], "description": "", "responses": { @@ -204,7 +204,7 @@ }, "post": { "tags": [ - "fabric" + "MedicalRecords" ], "description": "", "responses": { @@ -214,10 +214,10 @@ } } }, - "/assets/health": { + "/records/health": { "get": { "tags": [ - "fabric" + "MedicalRecords" ], "description": "", "responses": { @@ -227,15 +227,15 @@ } } }, - "/assets/{id}": { + "/records/{patientId}": { "get": { "tags": [ - "fabric" + "MedicalRecords" ], "description": "", "parameters": [ { - "name": "id", + "name": "patientId", "in": "path", "required": true, "type": "string" @@ -246,17 +246,15 @@ "description": "" } } - } - }, - "/assets/{id}/transfer": { + }, "put": { "tags": [ - "fabric" + "MedicalRecords" ], "description": "", "parameters": [ { - "name": "id", + "name": "patientId", "in": "path", "required": true, "type": "string" diff --git a/src/swagger.js b/src/swagger.js index 0b77177..6ea1337 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -8,8 +8,8 @@ const doc = { host: 'localhost:3000', schemes: ['http'], tags: [ - { name: 'auth', description: 'Authentication and account endpoints' }, - { name: 'fabric', description: 'Hyperledger Fabric asset endpoints' }, + { name: 'Auth', description: 'Authentication and account endpoints' }, + { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, ], }; From 5e6f755411626628db31dda8b4b8632ae07996a5 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 29 Nov 2025 23:48:58 +0200 Subject: [PATCH 034/210] fix: correct import statement for ValidationMiddleware in auth routes --- src/routes/auth.route.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ef2cf19..221f0b2 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -4,10 +4,10 @@ import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { GoogleAuthController } from '@/controllers/googleAuth.controller'; -import -/* #swagger.tags = ['Auth'] */ { ValidationMiddleware } from '@middlewares/validation.middleware'; +import { ValidationMiddleware } from '@middlewares/validation.middleware'; import { UpdateGoogleUserPhoneDto } from '@/dtos/googleUsers.dto'; -https://github.com/Blockchain-Based-EMR-System/Backendhttps://github.com/Blockchain-Based-EMR-System/Backend + + export class AuthRoute implements Routes { public path = '/auth'; public router = Router(); From c23a6989cf144e16a60962aa7bb7a46ce1b7a5f0 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 12 Nov 2025 00:57:35 +0200 Subject: [PATCH 035/210] db/ interface: Add medicalRecords --- docker-compose.yml | 3 +- src/interfaces/enums.interface.ts | 5 +++ src/interfaces/medicalRecords.interface.ts | 16 ++++++++++ .../migration.sql | 32 +++++++++++++++++++ src/prisma/schema.prisma | 28 ++++++++++++++++ test-ci.sh | 21 ++++++++++++ 6 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/medicalRecords.interface.ts create mode 100644 src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql create mode 100755 test-ci.sh diff --git a/docker-compose.yml b/docker-compose.yml index 43a54e1..0c62b9d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,9 @@ services: ports: - "3000:3000" - "5555:5555" + env_file: + - .env environment: - DATABASE_URL: "postgresql://NG:password@postgres:5432/EMR_DB?schema=public" NODE_ENV: development volumes: - ./:/app diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index bd853fb..f396d34 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -18,3 +18,8 @@ export enum Action { LOGIN = 'LOGIN', LOGOUT = 'LOGOUT', } + +export enum RecordType { + DIAGNOSIS = 'DIAGNOSIS', + VISIT_SUMMARY = 'VISIT_SUMMARY' +} diff --git a/src/interfaces/medicalRecords.interface.ts b/src/interfaces/medicalRecords.interface.ts new file mode 100644 index 0000000..79df7cd --- /dev/null +++ b/src/interfaces/medicalRecords.interface.ts @@ -0,0 +1,16 @@ +import { User } from './users.interface'; +import { RecordType } from './enums.interface'; + +export interface MedicalRecord { + id: string; + patient_id: string; + doctor_id?: string; + name: string; + cid: string; + type: RecordType; + created_at: Date; + modified_at: Date; + deleted_at?: Date; + + patient?: User; +} \ No newline at end of file diff --git a/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql b/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql new file mode 100644 index 0000000..4cb91f7 --- /dev/null +++ b/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "RecordType" AS ENUM ('LAB_RESULT', 'SCAN', 'DIAGNOSIS', 'VISIT_SUMMARY'); + +-- CreateTable +CREATE TABLE "MedicalRecords" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT, + "name" VARCHAR(255) NOT NULL, + "cid" VARCHAR(255) NOT NULL, + "type" "RecordType" NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "MedicalRecords_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "MedicalRecords_cid_key" ON "MedicalRecords"("cid"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_patient_id_idx" ON "MedicalRecords"("patient_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_doctor_id_idx" ON "MedicalRecords"("doctor_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_cid_idx" ON "MedicalRecords"("cid"); + +-- AddForeignKey +ALTER TABLE "MedicalRecords" ADD CONSTRAINT "MedicalRecords_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index e2bc5c7..e702aba 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -43,6 +43,7 @@ model User { audit_logs AuditLog[] @relation("UserAuditLogs") controlled_patients Patient[] @relation("ControllingNurse") refresh_tokens RefreshToken[] @relation("UserRefreshTokens") + medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") @@map("Users") } @@ -202,6 +203,25 @@ model AuditLog { @@map("AuditLogs") } +model MedicalRecord { + id String @id @default(uuid()) + patient_id String + doctor_id String? + name String @db.VarChar(255) + cid String @unique @db.VarChar(255) + type RecordType + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id], onDelete: Restrict) + + @@index([patient_id]) + @@index([doctor_id]) + @@index([cid]) + @@map("MedicalRecords") +} + model RefreshToken { id String @id @default(uuid()) user_id String @@ -252,3 +272,11 @@ enum Role { NURSE PATIENT } + + +enum RecordType { + LAB_RESULT + SCAN + DIAGNOSIS + VISIT_SUMMARY +} \ No newline at end of file diff --git a/test-ci.sh b/test-ci.sh new file mode 100755 index 0000000..d916943 --- /dev/null +++ b/test-ci.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +echo "Testing CI pipeline locally" +echo "" + +docker run -it --rm \ + -v $(pwd):/app \ + -w /app \ + node:22 \ + bash -c " + echo 'Installing dependencies' + npm ci + + echo 'Generating Prisma client' + npx prisma generate + + " + +echo "" +echo "✨ All CI checks passed! Safe to push yayyyy" From 3c2e51612c14562f9aa6be56013e75744ca39ccf Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 12 Nov 2025 03:02:17 +0200 Subject: [PATCH 036/210] add records dto --- src/dtos/medical-records.dto.ts | 30 ++++++++++++++++++++++++++++++ src/services/ipfs.service.ts | 0 2 files changed, 30 insertions(+) create mode 100644 src/dtos/medical-records.dto.ts create mode 100644 src/services/ipfs.service.ts diff --git a/src/dtos/medical-records.dto.ts b/src/dtos/medical-records.dto.ts new file mode 100644 index 0000000..260b43a --- /dev/null +++ b/src/dtos/medical-records.dto.ts @@ -0,0 +1,30 @@ +import { IsString, IsEnum, IsOptional, IsUUID } from "class-validator"; +import { RecordType } from "@/interfaces/enums.interface"; + + +// checks data when a patient uploads any MR +export class CreateMedicalRecordDto { + @IsString() + name: string; + + @IsEnum(RecordType) + type: RecordType; + + @IsOptional() + @IsUUID() + doctor_id?: string; + +} + +// permissions --> later + +// checks data when searching/filtering MR +export class GetMedicalRecordsDto { + @IsOptional() + @IsEnum(RecordType) + type?: RecordType; + + @IsOptional() + @IsUUID() + doctor_id?: string; +} \ No newline at end of file diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts new file mode 100644 index 0000000..e69de29 From 5f407b2ae6b9e2d680b686ed03ae4dd0cb805491 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 13 Nov 2025 00:26:13 +0200 Subject: [PATCH 037/210] implement main functionalities for IPFS service - uploadFile to handle file uploads to IPFS and return the generated cid - getFile to get files from IPFS using their cid - configure IPFS client connection (local) --- package-lock.json | 1341 ++++++++++++++++++++++- package.json | 1 + src/services/ipfs.service.ts | 49 + src/services/medical-records.service.ts | 19 + 4 files changed, 1376 insertions(+), 34 deletions(-) create mode 100644 src/services/medical-records.service.ts diff --git a/package-lock.json b/package-lock.json index 463d611..fe7c139 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "express-session": "^1.18.2", "helmet": "^8.1.0", "hpp": "^0.2.3", + "ipfs-http-client": "^60.0.1", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", "nodemailer": "^7.0.10", @@ -1254,6 +1255,21 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@chainsafe/is-ip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", + "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", + "license": "MIT" + }, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "license": "MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "license": "MIT", @@ -1456,6 +1472,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", @@ -1531,6 +1556,65 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@ipld/dag-cbor": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", + "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@ipld/dag-json": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.5.tgz", + "integrity": "sha512-Q4Fr3IBDEN8gkpgNefynJ4U/ZO5Kwr7WSUMBDbZx0c37t0+IwQCTM9yJh8l5L4SRFjm31MuHwniZ/kM+P7GQ3Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "cborg": "^4.0.0", + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-json/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@ipld/dag-pb": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz", + "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/@hyperledger/fabric-gateway": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", @@ -2055,6 +2139,342 @@ "version": "7.1.3", "license": "MIT" }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/interface-connection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", + "integrity": "sha512-6xx/NmEc84HX7QmsjSC3hHredQYjHv4Dkf4G27adAPf+qN+vnPxmQ7gaTnk243a0++DOFTbZ2gKX/15G2B6SRg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^1.0.4", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-connection/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-keychain": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@libp2p/interface-keychain/-/interface-keychain-2.0.5.tgz", + "integrity": "sha512-mb7QNgn9fIvC7CaJCi06GJ+a6DN6RVT9TmEi0NmedZGATeCArPeWWG7r7IfxNVXb9cVOOE1RzV1swK0ZxEJF9Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-info/-/interface-peer-info-1.0.10.tgz", + "integrity": "sha512-HQlo8NwQjMyamCHJrnILEZz+YwEOXCB2sIIw3slIrhVUYeYlTaia1R6d9umaAeLHa255Zmdm4qGH8rJLRqhCcg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-pubsub": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@libp2p/interface-pubsub/-/interface-pubsub-3.0.7.tgz", + "integrity": "sha512-+c74EVUBTfw2sx1GE/z/IjsYO6dhur+ukF0knAppeZsRQ1Kgg6K5R3eECtT28fC6dBWLjFpAvW/7QGfiDAL4RA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-connection": "^4.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "it-pushable": "^3.0.0", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interfaces": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", + "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", + "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.2", + "@multiformats/multiaddr": "^12.1.3", + "debug": "^4.3.4", + "interface-datastore": "^8.2.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/peer-id": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-2.0.4.tgz", + "integrity": "sha512-gcOsN8Fbhj6izIK+ejiWsqiqKeJ2yWPapi/m55VjOvDa52/ptQzZszxQP8jUk93u36de92ATFXDfZR/Bi6eeUQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.2.0", + "multiformats": "^11.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/dns": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", + "integrity": "sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "buffer": "^6.0.3", + "dns-packet": "^5.6.1", + "hashlru": "^2.3.0", + "p-queue": "^9.0.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/dns/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "node_modules/@multiformats/dns/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-11.6.1.tgz", + "integrity": "sha512-doST0+aB7/3dGK9+U5y3mtF3jq85KGbke1QiH0KE1F5mGQ9y56mFebTeu2D9FNOm+OT6UHb8Ss8vbSnpGjeLNw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "dns-over-http-resolver": "^2.1.0", + "err-code": "^3.0.1", + "multiformats": "^11.0.0", + "uint8arrays": "^4.0.2", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-9.0.8.tgz", + "integrity": "sha512-4eiN5iEiQfy2A98BxekUfW410L/ivg0sgjYSgSqmklnrBhK+QyMz4yqgfkub8xDTXOc7O5jp4+LVyM3ZqMeWNw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/@napi-rs/nice": { "version": "1.1.1", "dev": true, @@ -3566,6 +3986,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "license": "MIT" + }, "node_modules/@types/morgan": { "version": "1.9.10", "dev": true, @@ -4110,6 +4536,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/abort-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.1.tgz", + "integrity": "sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/accepts": { "version": "2.0.0", "license": "MIT", @@ -4248,6 +4680,12 @@ "node": ">=10" } }, + "node_modules/any-signal": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-3.0.1.tgz", + "integrity": "sha512-xgZgJtKEa9YmDqXodIgl7Fl1C8yNXr8w6gXjqK3LW4GcEiYT+6AQfJSE/8SPsEpLLmcvbv8YU+qet94UewHxqg==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "dev": true, @@ -4455,7 +4893,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "dev": true, "funding": [ { "type": "github", @@ -4566,6 +5003,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/blob-to-it": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-2.0.10.tgz", + "integrity": "sha512-I39vO57y+LBEIcAV7fif0sn96fYOYVqrPiOD+53MxQGv4DBgt1/HHZh0BHheWx2hVe24q5LTSXxqeV1Y3Nzkgg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "browser-readablestream-to-it": "^2.0.0" + } + }, "node_modules/bodec": { "version": "0.1.0", "dev": true, @@ -4636,6 +5082,12 @@ "node": ">=8" } }, + "node_modules/browser-readablestream-to-it": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-2.0.10.tgz", + "integrity": "sha512-I/9hEcRtjct8CzD9sVo9Mm4ntn0D+7tOVrjbPl69XAoOfgJ8NBdOQU+WX+5SHhcELJDb14mWt7zuvyqha+MEAQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/browserslist": { "version": "4.27.0", "dev": true, @@ -4888,6 +5340,15 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cborg": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.3.0.tgz", + "integrity": "sha512-vOXo1pB4mdeBw3LbpoynQlZNw/H3kZVHLtPYlp8kFMreL/2YfT54F70BM1s3iDoCtQ+3C9QmiRF4rfCSSTlhBw==", + "license": "Apache-2.0", + "bin": { + "cborg": "lib/bin.js" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -5377,6 +5838,16 @@ "dev": true, "license": "MIT" }, + "node_modules/dag-jose": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dag-jose/-/dag-jose-4.0.0.tgz", + "integrity": "sha512-tw595L3UYoOUT9dSJPbBEG/qpRpw24kRZxa5SLRnlnr+g5L7O8oEs1d3W5TiVA1oJZbthVsf0Vi3zFN66qcEBA==", + "license": "(Apache-2.0 OR MIT)", + "dependencies": { + "@ipld/dag-cbor": "^9.0.0", + "multiformats": "^11.0.0" + } + }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "dev": true, @@ -5556,6 +6027,30 @@ "node": ">=8" } }, + "node_modules/dns-over-http-resolver": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-2.1.3.tgz", + "integrity": "sha512-zjRYFhq+CsxPAouQWzOsxNMvEN+SHisjzhX8EMxd2Y0EG3thvn6wXQgMJLnTDImkhe4jhLbOQpXtL10nALBOSA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "debug": "^4.3.1", + "native-fetch": "^4.0.2", + "receptacle": "^1.3.2", + "undici": "^5.12.0" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "license": "Apache-2.0", @@ -5655,6 +6150,18 @@ "fast-check": "^3.23.1" } }, + "node_modules/electron-fetch": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.9.1.tgz", + "integrity": "sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA==", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.13" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.244", "dev": true, @@ -5697,9 +6204,7 @@ }, "node_modules/encoding": { "version": "0.1.13", - "dev": true, "license": "MIT", - "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -6102,7 +6607,6 @@ }, "node_modules/eventemitter3": { "version": "5.0.1", - "dev": true, "license": "MIT" }, "node_modules/events-universal": { @@ -6341,7 +6845,6 @@ }, "node_modules/fast-fifo": { "version": "1.3.2", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6792,6 +7295,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-iterator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-1.0.2.tgz", + "integrity": "sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==", + "license": "MIT" + }, "node_modules/get-package-type": { "version": "0.1.0", "dev": true, @@ -7052,6 +7561,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashlru": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", + "license": "MIT" + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -7218,7 +7733,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "dev": true, "funding": [ { "type": "github", @@ -7314,6 +7828,37 @@ "kind-of": "^6.0.2" } }, + "node_modules/interface-datastore": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", + "integrity": "sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^6.0.0", + "uint8arrays": "^5.1.0" + } + }, + "node_modules/interface-datastore/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/interface-datastore/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/interface-store": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.3.tgz", + "integrity": "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/ip-address": { "version": "10.0.1", "dev": true, @@ -7329,48 +7874,311 @@ "node": ">= 0.10" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "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", + "node_modules/ipfs-core-types": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.14.1.tgz", + "integrity": "sha512-4ujF8NlM9bYi2I6AIqPP9wfGGX0x/gRCkMoFdOQfxxrFg6HcAdfS+0/irK8mp4e7znOHWReOHeWqCGw+dAPwsw==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "Apache-2.0 OR MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "@ipld/dag-pb": "^4.0.0", + "@libp2p/interface-keychain": "^2.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.2", + "@libp2p/interface-pubsub": "^3.0.0", + "@multiformats/multiaddr": "^11.1.5", + "@types/node": "^18.0.0", + "interface-datastore": "^7.0.0", + "ipfs-unixfs": "^9.0.0", + "multiformats": "^11.0.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "dev": true, + "node_modules/ipfs-core-types/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "undici-types": "~5.26.4" + } + }, + "node_modules/ipfs-core-types/node_modules/interface-datastore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-7.0.4.tgz", + "integrity": "sha512-Q8LZS/jfFFHz6XyZazLTAc078SSCoa27ZPBOfobWdpDiFO7FqPA2yskitUJIhaCgxNK8C+/lMBUTBNfVIDvLiw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^3.0.0", + "nanoid": "^4.0.0", + "uint8arrays": "^4.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", + "node_modules/ipfs-core-types/node_modules/interface-store": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-3.0.4.tgz", + "integrity": "sha512-OjHUuGXbH4eXSBx1TF1tTySvjLldPLzRSYYXJwrEQI+XfH5JWYZofr0gVMV4F8XTwC+4V7jomDYkvGRmDSRKqQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-core-types/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==", + "license": "MIT" + }, + "node_modules/ipfs-core-utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/ipfs-core-utils/-/ipfs-core-utils-0.18.1.tgz", + "integrity": "sha512-P7jTpdfvlyBG3JR4o+Th3QJADlmXmwMxbkjszXry6VAjfSfLIIqXsdeYPoVRkV69GFEeQozuz2k/jR+U8cUH/Q==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/logger": "^2.0.5", + "@multiformats/multiaddr": "^11.1.5", + "@multiformats/multiaddr-to-uri": "^9.0.1", + "any-signal": "^3.0.0", + "blob-to-it": "^2.0.0", + "browser-readablestream-to-it": "^2.0.0", + "err-code": "^3.0.1", + "ipfs-core-types": "^0.14.1", + "ipfs-unixfs": "^9.0.0", + "ipfs-utils": "^9.0.13", + "it-all": "^2.0.0", + "it-map": "^2.0.0", + "it-peekable": "^2.0.0", + "it-to-stream": "^1.0.0", + "merge-options": "^3.0.4", + "multiformats": "^11.0.0", + "nanoid": "^4.0.0", + "parse-duration": "^1.0.0", + "timeout-abort-controller": "^3.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-core-utils/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/ipfs-http-client": { + "version": "60.0.1", + "resolved": "https://registry.npmjs.org/ipfs-http-client/-/ipfs-http-client-60.0.1.tgz", + "integrity": "sha512-amwM5TNuf077J+/q27jPHfatC05vJuIbX6ZnlYLjc2QsjOCKsORNBqV3brNw7l+fPrijV1yrwEDLG3JEnKsfMw==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-cbor": "^9.0.0", + "@ipld/dag-json": "^10.0.0", + "@ipld/dag-pb": "^4.0.0", + "@libp2p/logger": "^2.0.5", + "@libp2p/peer-id": "^2.0.0", + "@multiformats/multiaddr": "^11.1.5", + "any-signal": "^3.0.0", + "dag-jose": "^4.0.0", + "err-code": "^3.0.1", + "ipfs-core-types": "^0.14.1", + "ipfs-core-utils": "^0.18.1", + "ipfs-utils": "^9.0.13", + "it-first": "^2.0.0", + "it-last": "^2.0.0", + "merge-options": "^3.0.4", + "multiformats": "^11.0.0", + "parse-duration": "^1.0.0", + "stream-to-it": "^0.2.2", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-http-client/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/ipfs-unixfs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-9.0.1.tgz", + "integrity": "sha512-jh2CbXyxID+v3jLml9CqMwjdSS9ZRnsGfQGGPOfem0/hT/L48xUeTPvh7qLFWkZcIMhZtG+fnS1teei8x5uGBg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^7.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", + "node_modules/ipfs-unixfs/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/ipfs-utils": { + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-9.0.14.tgz", + "integrity": "sha512-zIaiEGX18QATxgaS0/EOQNoo33W0islREABAcxXE8n7y2MGAlB+hdsxXn4J0hGZge8IqVQhW8sWIb+oJz2yEvg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "any-signal": "^3.0.0", + "browser-readablestream-to-it": "^1.0.0", + "buffer": "^6.0.1", + "electron-fetch": "^1.7.2", + "err-code": "^3.0.1", + "is-electron": "^2.2.0", + "iso-url": "^1.1.5", + "it-all": "^1.0.4", + "it-glob": "^1.0.1", + "it-to-stream": "^1.0.0", + "merge-options": "^3.0.4", + "nanoid": "^3.1.20", + "native-fetch": "^3.0.0", + "node-fetch": "^2.6.8", + "react-native-fetch-api": "^3.0.0", + "stream-to-it": "^0.2.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-utils/node_modules/browser-readablestream-to-it": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz", + "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", + "license": "ISC" + }, + "node_modules/ipfs-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "node_modules/ipfs-utils/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/ipfs-utils/node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", + "license": "ISC" + }, + "node_modules/ipfs-utils/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/ipfs-utils/node_modules/native-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-3.0.0.tgz", + "integrity": "sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==", + "license": "MIT", + "peerDependencies": { + "node-fetch": "*" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", "dev": true, "license": "MIT", "dependencies": { @@ -7437,6 +8245,15 @@ "dev": true, "license": "ISC" }, + "node_modules/iso-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-1.2.1.tgz", + "integrity": "sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "dev": true, @@ -7498,6 +8315,154 @@ "node": ">=8" } }, + "node_modules/it-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-2.0.1.tgz", + "integrity": "sha512-9UuJcCRZsboz+HBQTNOau80Dw+ryGaHYFP/cPYzFBJBFcfDathMYnhHk4t52en9+fcyDGPTdLB+lFc1wzQIroA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-first": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-2.0.1.tgz", + "integrity": "sha512-noC1oEQcWZZMUwq7VWxHNLML43dM+5bviZpfmkxkXlvBe60z7AFRqpZSga9uQBo792jKv9otnn1IjA4zwgNARw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-glob": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-1.0.2.tgz", + "integrity": "sha512-Ch2Dzhw4URfB9L/0ZHyY+uqOnKvBNeS/SMcRiPmJfpHiM0TsUZn+GkpcZxAoF3dJVdPm/PuIk3A4wlV7SUo23Q==", + "license": "ISC", + "dependencies": { + "@types/minimatch": "^3.0.4", + "minimatch": "^3.0.4" + } + }, + "node_modules/it-glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/it-glob/node_modules/minimatch": { + "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" + }, + "engines": { + "node": "*" + } + }, + "node_modules/it-last": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-2.0.1.tgz", + "integrity": "sha512-uVMedYW0wa2Cx0TAmcOCLbfuLLII7+vyURmhKa8Zovpd+aBTMsmINtsta2n364wJ5qsEDBH+akY1sUtAkaYBlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-2.0.1.tgz", + "integrity": "sha512-a2GcYDHiAh/eSU628xlvB56LA98luXZnniH2GlD0IdBzf15shEq9rBeb0Rg3o1SWtNILUAwqmQxEXcewGCdvmQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-peekable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-2.0.1.tgz", + "integrity": "sha512-fJ/YTU9rHRhGJOM2hhQKKEfRM6uKB9r4yGGFLBHqp72ACC8Yi6+7/FhuBAMG8cpN6mLoj9auVX7ZJ3ul6qFpTA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-pushable": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", + "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "p-defer": "^4.0.0" + } + }, + "node_modules/it-stream-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-1.0.5.tgz", + "integrity": "sha512-I88Ka1nHgfX62e5mi5LLL+oueqz7Ltg0bUdtsUKDe9SoUqbQPf2Mp5kxDTe9pNhHQGs4pvYPAINwuZ1HAt42TA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-to-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/it-to-stream/-/it-to-stream-1.0.0.tgz", + "integrity": "sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "fast-fifo": "^1.0.0", + "get-iterator": "^1.0.2", + "p-defer": "^3.0.0", + "p-fifo": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/it-to-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "node_modules/it-to-stream/node_modules/p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "dev": true, @@ -8576,6 +9541,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, @@ -8891,6 +9877,16 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "dev": true, @@ -8919,6 +9915,24 @@ "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" } }, + "node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, "node_modules/napi-postinstall": { "version": "0.3.4", "dev": true, @@ -8933,6 +9947,15 @@ "url": "https://opencollective.com/napi-postinstall" } }, + "node_modules/native-fetch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-4.0.2.tgz", + "integrity": "sha512-4QcVlKFtv2EYVS5MBgsGX5+NWKtbDbIECdUXDBGDMAZXq3Jkv9zf+y8iS7Ub8fEdga3GpYeazp9gauNqXHJOCg==", + "license": "MIT", + "peerDependencies": { + "undici": "*" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -9007,6 +10030,26 @@ "node": ">=0.1.99" } }, + "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" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-fetch-native": { "version": "1.6.7", "devOptional": true, @@ -9386,6 +10429,37 @@ "node": ">=12.20" } }, + "node_modules/p-defer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", + "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-fifo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-fifo/-/p-fifo-1.0.0.tgz", + "integrity": "sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.0.0", + "p-defer": "^3.0.0" + } + }, + "node_modules/p-fifo/node_modules/p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "dev": true, @@ -9425,6 +10499,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", + "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "dev": true, @@ -9484,6 +10586,12 @@ "node": ">=6" } }, + "node_modules/parse-duration": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.2.tgz", + "integrity": "sha512-p8EIONG8L0u7f8GFgfVlL4n8rnChTt8O5FSxgxMz2tjc9FMP199wxVKVB6IbKx11uTbKHACSvaLVIKNnoeNR/A==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "dev": true, @@ -10104,6 +11212,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/progress-events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", + "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/promise-retry": { "version": "2.0.1", "dev": true, @@ -10325,6 +11439,24 @@ "dev": true, "license": "MIT" }, + "node_modules/react-native-fetch-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-native-fetch-api/-/react-native-fetch-api-3.0.0.tgz", + "integrity": "sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==", + "license": "MIT", + "dependencies": { + "p-defer": "^3.0.0" + } + }, + "node_modules/react-native-fetch-api/node_modules/p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/read": { "version": "1.0.7", "dev": true, @@ -10362,6 +11494,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/receptacle": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", + "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "license": "Apache-2.0" @@ -10499,6 +11640,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", + "license": "MIT" + }, "node_modules/retry": { "version": "0.12.0", "dev": true, @@ -10971,6 +12118,15 @@ "node": ">= 0.8" } }, + "node_modules/stream-to-it": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.4.tgz", + "integrity": "sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==", + "license": "MIT", + "dependencies": { + "get-iterator": "^1.0.2" + } + }, "node_modules/streamx": { "version": "2.23.0", "dev": true, @@ -11548,6 +12704,15 @@ "dev": true, "license": "MIT" }, + "node_modules/timeout-abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-3.0.0.tgz", + "integrity": "sha512-O3e+2B8BKrQxU2YRyEjC/2yFdb33slI22WRdUaDx6rvysfi9anloNZyR2q0l6LnePo5qH7gSM7uZtvvwZbc2yA==", + "license": "MIT", + "dependencies": { + "retimer": "^3.0.0" + } + }, "node_modules/tinyexec": { "version": "1.0.1", "devOptional": true, @@ -11616,6 +12781,12 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/triple-beam": { "version": "1.4.1", "license": "MIT", @@ -11951,6 +13122,31 @@ "node": ">=0.8.0" } }, + "node_modules/uint8-varint": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", + "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/uint8-varint/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/uint8-varint/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -11978,6 +13174,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/uint8arraylist": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", + "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^5.0.1" + } + }, + "node_modules/uint8arraylist/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/uint8arraylist/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "dev": true, @@ -11992,6 +13231,18 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "7.16.0", "license": "MIT" @@ -12132,6 +13383,12 @@ "node": ">= 0.10" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -12169,6 +13426,22 @@ "makeerror": "1.0.12" } }, + "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==", + "license": "BSD-2-Clause" + }, + "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" + } + }, "node_modules/which": { "version": "2.0.2", "dev": true, diff --git a/package.json b/package.json index fa9ede0..0c98bb8 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "express-session": "^1.18.2", "helmet": "^8.1.0", "hpp": "^0.2.3", + "ipfs-http-client": "^60.0.1", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", "nodemailer": "^7.0.10", diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index e69de29..2dac57b 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -0,0 +1,49 @@ +import { promises } from 'dns'; +import { port } from 'envalid' +import {create, IPFSHTTPClient} from 'ipfs-http-client' +import { HttpException } from '@/exceptions/HttpException'; + +// temp --> selecting the pinning service (4EVERLAND) +const ipfs_client: IPFSHTTPClient = create({ + host: process.env.IPFS_HOST, + port: parseInt(process.env.IPFS_PORT), + protocol: process.env.IPFS_PROTOCOL +}); + + +// upload med file to IPFS --> generate and return CID +export const uploadFile = async(fileData: Buffer, fileName: string): Promise => { + try{ + const result = await ipfs_client.add({ + path: fileName, + content: fileData, + }); + const cid = result.cid.toString(); + return cid + } + catch(e){ + console.error('failed to upload to IPFS', e); + throw new HttpException(500, 'failed to upload to IPFS'); + } +}; + + +// get file using CID +export const getFile = async (cid: string): Promise => { + try{ + // note --> each chunk in ipfs is Uint8Array + const chunks: Uint8Array[] = []; + + for await (const chunk of ipfs_client.cat(cid)){ + chunks.push(chunk); + } + const fileData = Buffer.concat(chunks); + return fileData; + } + catch(e){ + console.error('failed to retrieve from IPFS:', e) + throw new HttpException(404, 'file not found') + } +} + +// pin management --> TBD \ No newline at end of file diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts new file mode 100644 index 0000000..05f1c67 --- /dev/null +++ b/src/services/medical-records.service.ts @@ -0,0 +1,19 @@ +import { PrismaClient } from '@prisma/client'; +import { HttpException } from '@/exceptions/HttpException'; +import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { uploadFile, getFile } from '@/services/ipfs.service'; + +const prisma = new PrismaClient(); + +// create a new MR +// upload to IPFS --> get cid --> store on blockchain --> save to db + +// get all medical records for a specific patient + +// get a specific MR by id?? + +// delete any MR + +// get MR shared with a doctor + +// handle permissions --> fabric stuff \ No newline at end of file From 3064a40b439df2d9d2433b776ea7aca555fd5abd Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 16 Nov 2025 00:55:11 +0200 Subject: [PATCH 038/210] implement medical records service with ipfs integration - Add createMedicalRecord with ipfs upload and db storage - Add getPatientRecords to fetch all records for a specific patient - Add getDoctorRecords to get doctor records --- src/interfaces/enums.interface.ts | 4 +- src/interfaces/medicalRecords.interface.ts | 2 +- src/services/medical-records.service.ts | 87 +++++++++++++++++++++- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index f396d34..68994e2 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -20,6 +20,8 @@ export enum Action { } export enum RecordType { + LAB_RESULT = 'LAB_RESULT', + SCAN = 'SCAN', DIAGNOSIS = 'DIAGNOSIS', VISIT_SUMMARY = 'VISIT_SUMMARY' -} +} \ No newline at end of file diff --git a/src/interfaces/medicalRecords.interface.ts b/src/interfaces/medicalRecords.interface.ts index 79df7cd..6e78622 100644 --- a/src/interfaces/medicalRecords.interface.ts +++ b/src/interfaces/medicalRecords.interface.ts @@ -1,5 +1,5 @@ import { User } from './users.interface'; -import { RecordType } from './enums.interface'; +import { RecordType } from '@prisma/client'; export interface MedicalRecord { id: string; diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 05f1c67..3303b80 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -2,18 +2,99 @@ import { PrismaClient } from '@prisma/client'; import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { uploadFile, getFile } from '@/services/ipfs.service'; +import { MedicalRecord } from '@/interfaces/medicalRecords.interface'; const prisma = new PrismaClient(); // create a new MR -// upload to IPFS --> get cid --> store on blockchain --> save to db +export const createMedicalRecord = async( + patient_id: string, + fileData: CreateMedicalRecordDto, + fileBuffer: Buffer, + fileName: string, +): Promise => { + try{ + // upload to IPFS and get cid + const cid = await uploadFile(fileBuffer, fileName); + console.log(`file is uploaded to ipfs, cid:" ${cid}`) + + // blockchain stuff + + // save to db + const medicalRecord = await prisma.medicalRecord.create({ + data: { + patient_id: patient_id, + doctor_id: fileData.doctor_id || null, + name: fileData.name, + cid: cid, + type: fileData.type, + }, + include: { + patient: true, + }, + }); + + return medicalRecord; + + } + catch(e){ + console.error('error creating medical record:', e); + throw new HttpException(500, 'failed to create medical record'); + } +} + // get all medical records for a specific patient -// get a specific MR by id?? +export const getPatientRecords = async (patient_id: string): Promise => { + try{ + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patient_id, + deleted_at: null, + }, + orderBy:{ + created_at: 'desc', + }, + include: { + patient: true, + }, + }); -// delete any MR + return records; + } + catch(e){ + console.error('error fetching patient records:', e); + throw new HttpException(500, 'failed to fetch patient records'); + } +}; // get MR shared with a doctor +export const getDoctorRecords = async(doctor_id: string): Promise => { + try{ + const records = await prisma.medicalRecord.findMany({ + where:{ + doctor_id: doctor_id, + deleted_at: null, + }, + orderBy:{ + created_at: 'desc', + }, + include:{ + patient: true, + }, + }); + + return records; + } + catch(e){ + console.error('error fetching doctor records:', e); + throw new HttpException(500, 'failed to fetch doctor records'); + } +} + +// get a specific MR by id?? + +// delete any MR (soft) // handle permissions --> fabric stuff \ No newline at end of file From 6bbab5f1892954932b9cb2ef944b0177c76d199d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 19 Nov 2025 06:20:31 +0200 Subject: [PATCH 039/210] MR upload middleware / MR deletion --- package-lock.json | 142 +++++++++++++++++++++++- package.json | 2 + src/middlewares/upload.middleware.ts | 39 +++++++ src/services/medical-records.service.ts | 55 ++++++--- 4 files changed, 221 insertions(+), 17 deletions(-) create mode 100644 src/middlewares/upload.middleware.ts diff --git a/package-lock.json b/package-lock.json index fe7c139..54d2b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "multer": "^2.0.2", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", @@ -51,6 +52,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", @@ -4005,6 +4007,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -4711,6 +4723,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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==", + "license": "MIT" + }, "node_modules/arch": { "version": "3.0.0", "dev": true, @@ -5176,9 +5194,19 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, "license": "MIT" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -5720,6 +5748,21 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/confbox": { "version": "0.2.2", "devOptional": true, @@ -9682,7 +9725,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9877,6 +9919,79 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/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/multer/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/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/multiformats": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", @@ -12127,6 +12242,14 @@ "get-iterator": "^1.0.2" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.23.0", "dev": true, @@ -13094,6 +13217,12 @@ "node": ">= 0.6" } }, + "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==", + "license": "MIT" + }, "node_modules/typedi": { "version": "0.10.0", "license": "MIT" @@ -13653,6 +13782,15 @@ } } }, + "node_modules/xtend": { + "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" + } + }, "node_modules/y18n": { "version": "5.0.8", "license": "ISC", diff --git a/package.json b/package.json index 0c98bb8..8532d3f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "multer": "^2.0.2", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", @@ -64,6 +65,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", diff --git a/src/middlewares/upload.middleware.ts b/src/middlewares/upload.middleware.ts new file mode 100644 index 0000000..2a7fd36 --- /dev/null +++ b/src/middlewares/upload.middleware.ts @@ -0,0 +1,39 @@ +import multer, { FileFilterCallback } from "multer"; +import { Request } from 'express'; +import { HttpException } from "@/exceptions/HttpException"; + + +const storage = multer.memoryStorage(); +const allowed_file_types = [ + 'application/pdf', + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/gif', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'text/plain', +]; + + +const fileFilter = (req: Request, file: Express.Multer.File, cb: FileFilterCallback) => { + if (allowed_file_types.includes(file.mimetype)) { + cb(null, true); + } + else { + cb(new HttpException(400, `file type not allowed`)) + } +}; + + +const upload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 300 * 1024 * 1024, + }, +}); + + +export const uploadSingleFile = upload.single('file'); +export const uploadMultipleFiles = upload.array('files', 3); \ No newline at end of file diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 3303b80..dce2374 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -7,13 +7,13 @@ import { MedicalRecord } from '@/interfaces/medicalRecords.interface'; const prisma = new PrismaClient(); // create a new MR -export const createMedicalRecord = async( +export const createMedicalRecord = async ( patient_id: string, fileData: CreateMedicalRecordDto, fileBuffer: Buffer, fileName: string, ): Promise => { - try{ + try { // upload to IPFS and get cid const cid = await uploadFile(fileBuffer, fileName); console.log(`file is uploaded to ipfs, cid:" ${cid}`) @@ -33,11 +33,11 @@ export const createMedicalRecord = async( patient: true, }, }); - + return medicalRecord; } - catch(e){ + catch (e) { console.error('error creating medical record:', e); throw new HttpException(500, 'failed to create medical record'); } @@ -47,13 +47,13 @@ export const createMedicalRecord = async( // get all medical records for a specific patient export const getPatientRecords = async (patient_id: string): Promise => { - try{ + try { const records = await prisma.medicalRecord.findMany({ where: { patient_id: patient_id, deleted_at: null, }, - orderBy:{ + orderBy: { created_at: 'desc', }, include: { @@ -63,38 +63,63 @@ export const getPatientRecords = async (patient_id: string): Promise => { - try{ +export const getDoctorRecords = async (doctor_id: string): Promise => { + try { const records = await prisma.medicalRecord.findMany({ - where:{ + where: { doctor_id: doctor_id, deleted_at: null, }, - orderBy:{ + orderBy: { created_at: 'desc', }, - include:{ + include: { patient: true, }, }); return records; } - catch(e){ + catch (e) { console.error('error fetching doctor records:', e); throw new HttpException(500, 'failed to fetch doctor records'); } } -// get a specific MR by id?? - // delete any MR (soft) +export const deleteRecord = async (record_id: string) => { + try { + // checking if it's already deleted + const record = await prisma.medicalRecord.findFirst({ + where: { + id: record_id, + deleted_at: null, + }, + }); + if (!record) { + throw new HttpException(404, 'medical record not found or already deleted'); + } + await prisma.medicalRecord.update({ + where: { + id: record_id, + }, + data: { + deleted_at: new Date(), + }, + }); + } + catch (e) { + console.error('Error deleting medical record:', e); + throw new HttpException(500, 'failed to delete medical record'); + } +} +// get a specific MR by id?? // handle permissions --> fabric stuff \ No newline at end of file From aeabd517de2bd22e88d7954b43ec5f7e01ac8dd4 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 5 Dec 2025 18:23:45 +0200 Subject: [PATCH 040/210] create MRs controller --- src/controllers/medical-records.controller.ts | 79 +++++++++++++++++++ src/middlewares/permissions.middleware.ts | 10 +++ 2 files changed, 89 insertions(+) create mode 100644 src/controllers/medical-records.controller.ts create mode 100644 src/middlewares/permissions.middleware.ts diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts new file mode 100644 index 0000000..da72423 --- /dev/null +++ b/src/controllers/medical-records.controller.ts @@ -0,0 +1,79 @@ +import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { Request, Response, NextFunction } from 'express'; +import * as MedicalRecordService from '@/services/medical-records.service' +import { RequestWithUser } from '@/interfaces/auth.interface'; +import { promises } from 'dns'; + + +// upload a new medical record +export const uploadRecord = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + try{ + if (!req.file){ + res.status(400).json({message: 'No file uploaded'}); + } + + const record_data: CreateMedicalRecordDto = req.body; + const patient_id = req.user.id; + const file_buffer = req.file.buffer; + const file_name = req.file.originalname; + + const medical_record = await MedicalRecordService.createMedicalRecord(patient_id, record_data, file_buffer, file_name); + + res.status(201).json({ + message: 'uploaded MR successfully', + data: medical_record, + }); + } + catch(e){ + next(e); + } +}; + + +// get all MRs for a patient + +export const getPatientMedicalRecords = async (req: RequestWithUser, res: Response, next: NextFunction): Promise =>{ + try{ + const patient_id = req.user.id; + + const records = await MedicalRecordService.getPatientRecords(patient_id); + res.status(201).json({ + message: 'MRs retrieved successfully', + data: records, + }); + } + catch(e){ + next(e); + } +}; + + +// get all MRs for a doctor +export const getDocrotMedicalRecords = async (req: RequestWithUser, res: Response, next:NextFunction): Promise => { + try{ + const doctor_id = req.user.id; + + const records = await MedicalRecordService.getDoctorRecords(doctor_id); + res.status(201).json({ + message: 'MRs retrieved successfully', + data: records, + }); + } + catch(e){ + next(e); + } +}; + +export const deleteRecord = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const record_id = req.params.id; + + await MedicalRecordService.deleteRecord(record_id); + + res.status(200).json({ + message: 'deleted MR successfully', + }); + } catch (e) { + next(e); + } +}; \ No newline at end of file diff --git a/src/middlewares/permissions.middleware.ts b/src/middlewares/permissions.middleware.ts new file mode 100644 index 0000000..0a1659c --- /dev/null +++ b/src/middlewares/permissions.middleware.ts @@ -0,0 +1,10 @@ +import { Request, Response, NextFunction } from 'express'; +import { PrismaClient } from '@prisma/client'; +import { HttpException } from '@/exceptions/HttpException'; + + +const prisma = new PrismaClient(); + +// check if user owns MR + +// check if user can view MR \ No newline at end of file From 84424ddb289a516d8ced98b9247d9accd3eaa16a Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 11 Dec 2025 01:39:43 +0200 Subject: [PATCH 041/210] create a shared prisma instance --- src/config/prisma.ts | 5 +++++ src/interfaces/medicalRecords.interface.ts | 3 ++- src/middlewares/auth.middleware.ts | 4 ++-- src/services/auth.service.ts | 9 +++++---- src/services/googleAuth.service.ts | 2 +- src/services/medical-records.service.ts | 3 +-- 6 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 src/config/prisma.ts diff --git a/src/config/prisma.ts b/src/config/prisma.ts new file mode 100644 index 0000000..b904402 --- /dev/null +++ b/src/config/prisma.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export default prisma; \ No newline at end of file diff --git a/src/interfaces/medicalRecords.interface.ts b/src/interfaces/medicalRecords.interface.ts index 6e78622..559a9a2 100644 --- a/src/interfaces/medicalRecords.interface.ts +++ b/src/interfaces/medicalRecords.interface.ts @@ -13,4 +13,5 @@ export interface MedicalRecord { deleted_at?: Date; patient?: User; -} \ No newline at end of file +} + diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index c9ccf9b..17edec1 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -1,4 +1,3 @@ -import { PrismaClient } from '@prisma/client'; import { NextFunction, Response, Request } from 'express'; import { verify } from 'jsonwebtoken'; import { SECRET_KEY } from '@config'; @@ -6,6 +5,7 @@ import { HttpException } from '@exceptions/HttpException'; import { DataStoredInToken, RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@/interfaces'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; +import prisma from '@/config/prisma'; const getAuthorization = (req: Request) => { const cookie = req.cookies['Authorization']; @@ -23,7 +23,7 @@ export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: const Authorization = getAuthorization(req); if (Authorization) { const { id } = (await verify(Authorization, SECRET_KEY)) as DataStoredInToken; - const users = new PrismaClient().user; + const users = prisma.user; const findUser: User = await users.findUnique({ where: { id } }); if (findUser) { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index ad5814b..63d205d 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { PrismaClient, Role } from '@prisma/client'; +import { Role } from '@prisma/client'; import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; @@ -10,12 +10,13 @@ import { UserLoginData, User } from '@interfaces/users.interface'; import { transporter } from '@/utils/nodeMailerService'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import crypto from 'crypto'; +import prisma from '@/config/prisma'; @Service() export class AuthService { - public users = new PrismaClient().user; - public patients = new PrismaClient().patient; - public refreshTokens = new PrismaClient().refreshToken; + public users = prisma.user; + public patients = prisma.patient; + public refreshTokens = prisma.refreshToken; public async signup(userData: CreateUserDto): Promise<{ createdUserData: User; cookies: string[] }> { const findUserSameEmail: User = await this.users.findUnique({ where: { email: userData.email } }); diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index b5b13cb..b13983d 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -4,8 +4,8 @@ import { PrismaClient } from "@prisma/client"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; +import prisma from "@/config/prisma"; -const prisma = new PrismaClient(); @Service() export class GoogleAuthService { diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index dce2374..0e1a25d 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -3,8 +3,7 @@ import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { uploadFile, getFile } from '@/services/ipfs.service'; import { MedicalRecord } from '@/interfaces/medicalRecords.interface'; - -const prisma = new PrismaClient(); +import prisma from '@/config/prisma'; // create a new MR export const createMedicalRecord = async ( From 9f8ff39d1aed05495085ac6329281c9bc2de0c2e Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 12 Dec 2025 01:28:58 +0200 Subject: [PATCH 042/210] feat: Implement Fabric identity management and connection caching --- .gitignore | 3 + package-lock.json | 98 +++-- src/controllers/fabric.controller.ts | 116 ++++- src/dtos/fabric-identity.dto.ts | 38 ++ src/interfaces/fabric-identity.interface.ts | 26 ++ src/routes/auth.route.ts | 153 ++++++- src/routes/fabric.route.ts | 125 +++++- src/services/fabric.service.ts | 238 +++++++---- src/services/identity-storage.service.ts | 215 ++++++++++ src/swagger-output.json | 445 ++++++++++++++++++++ src/swagger.js | 2 +- 11 files changed, 1299 insertions(+), 160 deletions(-) create mode 100644 src/dtos/fabric-identity.dto.ts create mode 100644 src/interfaces/fabric-identity.interface.ts create mode 100644 src/services/identity-storage.service.ts diff --git a/.gitignore b/.gitignore index 0ccb8df..abe9c54 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,6 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +# fabric files +fabric-identities.json diff --git a/package-lock.json b/package-lock.json index 54d2b42..991a339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,10 +27,10 @@ "ipfs-http-client": "^60.0.1", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "multer": "^2.0.2", "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", - "multer": "^2.0.2", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", @@ -1558,6 +1558,37 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", + "integrity": "sha512-q5lFrzbKsKdMgMGhaEE4dVXtpQa4qyWMdD1RXJFki6BiiKOzZC7IEV3xj67ffSaD33iYztxomYxlHVQJqD21HQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-protos": "^0.3.0", + "@noble/curves": "^1.9.4", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "pkcs11js": "^2.1.0" + } + }, + "node_modules/@hyperledger/fabric-protos": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", + "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.11.0", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=16.13.0" + } + }, "node_modules/@ipld/dag-cbor": { "version": "9.2.5", "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", @@ -1617,37 +1648,6 @@ "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", "license": "Apache-2.0 OR MIT" }, - "node_modules/@hyperledger/fabric-gateway": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", - "integrity": "sha512-q5lFrzbKsKdMgMGhaEE4dVXtpQa4qyWMdD1RXJFki6BiiKOzZC7IEV3xj67ffSaD33iYztxomYxlHVQJqD21HQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.0", - "@hyperledger/fabric-protos": "^0.3.0", - "@noble/curves": "^1.9.4", - "google-protobuf": "^3.21.0" - }, - "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "pkcs11js": "^2.1.0" - } - }, - "node_modules/@hyperledger/fabric-protos": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", - "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.11.0", - "google-protobuf": "^3.21.0" - }, - "engines": { - "node": ">=16.13.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -7764,9 +7764,7 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", - "dev": true, "license": "MIT", - "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -13251,6 +13249,22 @@ "node": ">=0.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/uint8-varint": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", @@ -13276,22 +13290,6 @@ "multiformats": "^13.0.0" } }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uid2": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" - }, "node_modules/uint8array-extras": { "version": "1.5.0", "dev": true, diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts index 69de8d7..5ce8225 100644 --- a/src/controllers/fabric.controller.ts +++ b/src/controllers/fabric.controller.ts @@ -1,12 +1,98 @@ import { NextFunction, Request, Response } from 'express'; import FabricService from '@/services/fabric.service'; +import identityStorage from '@/services/identity-storage.service'; +import { FabricIdentityInput } from '@/interfaces/fabric-identity.interface'; +import { HttpException } from '@/exceptions/HttpException'; class FabricController { public fabricService = new FabricService(); + /** + * Extract identity label from request header + */ + private getIdentityLabel(req: Request): string { + const identityLabel = req.headers['x-fabric-identity'] as string; + if (!identityLabel) { + throw new HttpException(400, 'Missing X-Fabric-Identity header'); + } + return identityLabel; + } + + /** + * Onboard a new organization identity + * POST /fabric/onboard + */ + public onboardIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const input: FabricIdentityInput = req.body; + + // Validate required fields + if (!input.label || !input.mspId || !input.certificate || + !input.privateKey || !input.peerEndpoint || !input.peerHostAlias || + !input.tlsCertificate) { + throw new HttpException(400, 'Missing required fields: label, mspId, certificate, privateKey, peerEndpoint, peerHostAlias, tlsCertificate'); + } + + const identity = await identityStorage.storeIdentity(input); + res.status(201).json({ + data: identity, + message: 'Identity onboarded successfully' + }); + } catch (error) { + next(error); + } + }; + + /** + * List all stored identities (without sensitive data) + * GET /fabric/identities + */ + public listIdentities = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const identities = await identityStorage.listIdentities(); + res.status(200).json({ data: identities, message: 'listIdentities' }); + } catch (error) { + next(error); + } + }; + + /** + * Delete an identity + * DELETE /fabric/identities/:label + */ + public deleteIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const label = req.params.label; + + // Close any active connection for this identity + await this.fabricService.closeConnection(label); + + // Delete from storage + await identityStorage.deleteIdentity(label); + + res.status(200).json({ message: 'Identity deleted successfully' }); + } catch (error) { + next(error); + } + }; + + /** + * Get connection statistics + * GET /fabric/connections + */ + public getConnectionStats = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const stats = this.fabricService.getConnectionStats(); + res.status(200).json({ data: stats, message: 'connectionStats' }); + } catch (error) { + next(error); + } + }; + public getAllRecords = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const records = await this.fabricService.getAllRecords(); + const identityLabel = this.getIdentityLabel(req); + const records = await this.fabricService.getAllRecords(identityLabel); res.status(200).json({ data: records, message: 'findAll' }); } catch (error) { next(error); @@ -15,8 +101,9 @@ class FabricController { public getRecordById = async (req: Request, res: Response, next: NextFunction): Promise => { try { + const identityLabel = this.getIdentityLabel(req); const patientId = req.params.patientId; - const record = await this.fabricService.getRecordByPatientId(patientId); + const record = await this.fabricService.getRecordByPatientId(identityLabel, patientId); res.status(200).json({ data: record, message: 'findOne' }); } catch (error) { next(error); @@ -25,7 +112,8 @@ class FabricController { public addRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { - await this.fabricService.addRecord(req.body); + const identityLabel = this.getIdentityLabel(req); + await this.fabricService.addRecord(identityLabel, req.body); res.status(201).json({ message: 'created' }); } catch (error) { next(error); @@ -34,17 +122,33 @@ class FabricController { public updateRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { + const identityLabel = this.getIdentityLabel(req); const patientId = req.params.patientId; - await this.fabricService.updateRecord(patientId, req.body); + await this.fabricService.updateRecord(identityLabel, patientId, req.body); res.status(200).json({ message: 'updated' }); } catch (error) { next(error); } }; + + public initLedger = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const identityLabel = this.getIdentityLabel(req); + await this.fabricService.initLedger(identityLabel); + res.status(200).json({ message: 'Ledger initialized' }); + } catch (error) { + next(error); + } + }; + public checkHealth = async (req: Request, res: Response, next: NextFunction): Promise => { try { - // Simple health check logic - res.status(200).json({ status: 'OK', message: 'Fabric service is healthy' }); + const stats = this.fabricService.getConnectionStats(); + res.status(200).json({ + status: 'OK', + message: 'Fabric service is healthy', + activeConnections: stats.total + }); } catch (error) { next(error); } diff --git a/src/dtos/fabric-identity.dto.ts b/src/dtos/fabric-identity.dto.ts new file mode 100644 index 0000000..0820560 --- /dev/null +++ b/src/dtos/fabric-identity.dto.ts @@ -0,0 +1,38 @@ +import { IsString, IsOptional, IsNotEmpty } from 'class-validator'; +export class OnboardIdentityDto { + @IsString() + @IsNotEmpty() + public label: string; + + @IsString() + @IsNotEmpty() + public mspId: string; + + @IsString() + @IsNotEmpty() + public certificate: string; + + @IsString() + @IsNotEmpty() + public privateKey: string; + + @IsString() + @IsNotEmpty() + public peerEndpoint: string; + + @IsString() + @IsNotEmpty() + public peerHostAlias: string; + + @IsString() + @IsNotEmpty() + public tlsCertificate: string; + + @IsString() + @IsOptional() + public channelName?: string; + + @IsString() + @IsOptional() + public chaincodeName?: string; +} diff --git a/src/interfaces/fabric-identity.interface.ts b/src/interfaces/fabric-identity.interface.ts new file mode 100644 index 0000000..2e7a447 --- /dev/null +++ b/src/interfaces/fabric-identity.interface.ts @@ -0,0 +1,26 @@ + +export interface FabricIdentity { + label: string; + mspId: string; + certificate: string; + privateKey: string; + peerEndpoint: string; + peerHostAlias: string; + tlsCertificate: string; + channelName: string; + chaincodeName: string; + createdAt: string; + updatedAt: string; +} + +export interface FabricIdentityInput { + label: string; + mspId: string; + certificate: string; + privateKey: string; + peerEndpoint: string; + peerHostAlias: string; + tlsCertificate: string; + channelName?: string; + chaincodeName?: string; +} diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 221f0b2..c2a8a0d 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -21,63 +21,168 @@ export class AuthRoute implements Routes { private initializeRoutes() { this.router.post( `/auth/signup`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User signup data', + required: true, + schema: { + $email: 'user@example.com', + $name: 'John Doe', + $phone: '1234567890', + $password: 'password123' + } + } + */ ValidationMiddleware(CreateUserDto), this.auth.signUp, ); this.router.post( `/auth/login`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User login data', + required: true, + schema: { + $emailOrUsername: 'user@example.com', + $password: 'password123', + rememberMe: false + } + } + */ ValidationMiddleware(LoginUserDto), this.auth.logIn, ); this.router.post( `/auth/logout`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token or cookie (e.g. Authorization: Bearer )', + required: true, + type: 'string' + } + */ AuthMiddleware, this.auth.logOut, ); this.router.post( `/auth/refresh`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Refresh token in cookie or Authorization header. If using cookie, ensure cookies are sent.', + required: true, + type: 'string' + } + */ AuthMiddleware, this.auth.refresh, ); this.router.patch( `/auth/complete-profile-info`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Complete user profile', + required: true, + schema: { + $gender: 'Male', + $date_of_birth: '1990-01-01' + } + } + */ ValidationMiddleware(CompleteUserProfileDto), + /* + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ AuthMiddleware, this.auth.completeProfile, ); this.router.patch( `/auth/verify-otp`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Verify OTP', + required: true, + schema: { + $otp: '123456' + } + } + */ AuthMiddleware, this.auth.verifyOTP, ); this.router.post( `/auth/forget-password`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Request password reset', + required: true, + schema: { + $email: 'user@example.com' + } + } + */ this.auth.forgetPassword, ); this.router.post( `/auth/reset-password`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Reset password', + required: true, + schema: { + $token: 'reset-token', + $newPassword: 'newPassword123' + } + } + */ ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword, ); this.router.post( `/auth/resend-otp`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ AuthMiddleware, this.auth.resendOTP, ); @@ -96,15 +201,41 @@ export class AuthRoute implements Routes { this.router.patch( `/auth/google/update-phone`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Update Google user phone', + required: true, + schema: { + $phone: '1234567890' + } + } + */ ValidationMiddleware(UpdateGoogleUserPhoneDto), + /* + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ AuthMiddleware, this.googleAuth.updatePhoneNumber, ); this.router.get( `/auth/google/userData`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ AuthMiddleware, this.googleAuth.getGoogleUserData, ); diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index 08b360a..6ff12ff 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -1,6 +1,7 @@ import { Router } from 'express'; import FabricContoller from '@/controllers/fabric.controller'; import { CreateMedicalRecordDto, UpdateMedicalRecordDto } from '@/dtos/medicalRecord.dto'; +import { OnboardIdentityDto } from '@/dtos/fabric-identity.dto'; import { ValidationMiddleware } from '@middlewares/validation.middleware'; import { Routes } from '@interfaces/routes.interface'; @@ -14,9 +15,72 @@ export class FabricRoute implements Routes { } private initializeRoutes() { + // Identity Management Routes + this.router.post( + '/fabric/onboard', + /* + #swagger.tags = ['FabricIdentity'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Identity onboarding data', + required: true, + schema: { + $label: 'org1', + $mspId: 'Org1MSP', + $certificate: 'PEM certificate', + $privateKey: 'PEM private key', + $peerEndpoint: 'localhost:7051', + $peerHostAlias: 'peer0.org1.example.com', + $tlsCertificate: 'PEM TLS certificate', + channelName: 'mychannel', + chaincodeName: 'test' + } + } + */ + ValidationMiddleware(OnboardIdentityDto), + this.fabricController.onboardIdentity, + ); + this.router.get( + '/fabric/identities', + /* #swagger.tags = ['FabricIdentity'] */ + this.fabricController.listIdentities, + ); + this.router.delete( + '/fabric/identities/:label', + /* #swagger.tags = ['FabricIdentity'] */ + this.fabricController.deleteIdentity, + ); + this.router.get( + '/fabric/connections', + /* #swagger.tags = ['FabricIdentity'] */ + this.fabricController.getConnectionStats, + ); + this.router.post( + '/fabric/init-ledger', + /* + #swagger.tags = ['FabricIdentity'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + */ + this.fabricController.initLedger, + ); + + // Medical Records Routes (require X-Fabric-Identity header) this.router.get( '/records', - /* #swagger.tags = ['MedicalRecords'] */ + /* + #swagger.tags = ['MedicalRecords'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + */ this.fabricController.getAllRecords, ); // Place the explicit health route before the dynamic `:patientId` route so the literal @@ -28,18 +92,71 @@ export class FabricRoute implements Routes { ); this.router.get( '/records/:patientId', - /* #swagger.tags = ['MedicalRecords'] */ + /* + #swagger.tags = ['MedicalRecords'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + */ this.fabricController.getRecordById, ); this.router.post( '/records', - /* #swagger.tags = ['MedicalRecords'] */ + /* + #swagger.tags = ['MedicalRecords'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Medical record data', + required: true, + schema: { + $patientId: 'P12345', + $firstName: 'John', + $lastName: 'Doe', + $dateOfBirth: '1990-01-01', + $gender: 'Male', + $bloodType: 'O+', + $ipfsCid: 'Qm...', + summary: 'Optional summary' + } + } + */ ValidationMiddleware(CreateMedicalRecordDto), this.fabricController.addRecord, ); this.router.put( '/records/:patientId', - /* #swagger.tags = ['MedicalRecords'] */ + /* + #swagger.tags = ['MedicalRecords'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Update medical record data', + required: true, + schema: { + $firstName: 'John', + $lastName: 'Doe', + $dateOfBirth: '1990-01-01', + $gender: 'Male', + $bloodType: 'O+', + $ipfsCid: 'Qm...', + summary: 'Optional summary' + } + } + */ ValidationMiddleware(UpdateMedicalRecordDto), this.fabricController.updateRecord, ); diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index dd920f7..e24a0e9 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -1,70 +1,123 @@ import * as grpc from '@grpc/grpc-js'; import { connect, Contract, Gateway, Identity, Signer, signers } from '@hyperledger/fabric-gateway'; import * as crypto from 'crypto'; -import { promises as fs } from 'fs'; -import * as path from 'path'; import { TextDecoder } from 'util'; import { HttpException } from '@/exceptions/HttpException'; import { MedicalRecord } from '@/interfaces/medical-records.interface'; +import { FabricIdentity } from '@/interfaces/fabric-identity.interface'; +import identityStorage from '@/services/identity-storage.service'; + + +interface GatewayConnection { + gateway: Gateway; + client: grpc.Client; + contract: Contract; + identity: FabricIdentity; + lastUsed: Date; +} class FabricService { - private gateway: Gateway | undefined; - private contract: Contract | undefined; private readonly utf8Decoder = new TextDecoder(); - - // Configuration - should be moved to your config/index.ts and .env file - private readonly channelName = process.env.CHANNEL_NAME || 'mychannel'; - private readonly chaincodeName = process.env.CHAINCODE_NAME || 'test'; - private readonly mspId = process.env.MSP_ID || 'Org1MSP'; - private readonly cryptoPath = process.env.CRYPTO_PATH || path.resolve(__dirname, '../../../Blockchain/test-network/organizations/peerOrganizations/org1.example.com'); - private readonly keyDirectoryPath = process.env.KEY_DIRECTORY_PATH || path.resolve(this.cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'); - private readonly certDirectoryPath = process.env.CERT_DIRECTORY_PATH || path.resolve(this.cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts'); - private readonly tlsCertPath = process.env.TLS_CERT_PATH || path.resolve(this.cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); - private readonly peerEndpoint = process.env.PEER_ENDPOINT || 'localhost:7051'; - private readonly peerHostAlias = process.env.PEER_HOST_ALIAS || 'peer0.org1.example.com'; - private client: grpc.Client | undefined; + + // Connection cache with TTL + private connections: Map = new Map(); + private readonly CONNECTION_TTL_MS = 30 * 60 * 1000; // 30 minutes + private cleanupInterval: NodeJS.Timeout | null = null; constructor() { - this.connectToNetwork().catch(error => { - console.error('Failed to connect to Fabric network on initialization:', error); - process.exit(1); - }); + this.startCleanupInterval(); + } + + public async getGatewayConnection(identityLabel: string): Promise { + + const cached = this.connections.get(identityLabel); + if (cached) { + cached.lastUsed = new Date(); + return cached; + } + + const identity = await identityStorage.getIdentity(identityLabel); + const connection = await this.createConnection(identity); + this.connections.set(identityLabel, connection); + + console.log(`✅ Created new gateway connection for: ${identityLabel}`); + return connection; } - private async connectToNetwork(): Promise { + private async createConnection(identity: FabricIdentity): Promise { try { - this.client = await this.newGrpcConnection(); - this.gateway = connect({ - client: this.client, - identity: await this.newIdentity(), - signer: await this.newSigner(), + + const client = await this.newGrpcConnection(identity); + + + const gateway = connect({ + client, + identity: this.createIdentity(identity), + signer: this.createSigner(identity), }); - const network = this.gateway.getNetwork(this.channelName); - this.contract = network.getContract(this.chaincodeName); - await this.initLedger(); - console.log('*** Fabric Service Initialized and Ledger Ready ***'); - } catch (error) { - console.error('fabric network not connected'); - // Do not throw to avoid crashing the application during startup. - // Leave gateway/client/contract undefined so callers can detect uninitialized service. - this.gateway = undefined; - this.client = undefined; - this.contract = undefined; - return; + + + const network = gateway.getNetwork(identity.channelName); + const contract = network.getContract(identity.chaincodeName); + + return { + gateway, + client, + contract, + identity, + lastUsed: new Date(), + }; + } catch (error: any) { + console.error(`❌ Failed to create connection for ${identity.label}:`, error.message); + throw new HttpException(503, `Failed to connect to Fabric network: ${error.message}`); } } - public async getAllRecords(): Promise { - const contract = this.ensureContract(); - console.log('\n--> Evaluate Transaction: GetAllRecords'); + private async newGrpcConnection(identity: FabricIdentity): Promise { + const tlsRootCert = Buffer.from(identity.tlsCertificate); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + + return new grpc.Client(identity.peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': identity.peerHostAlias, + 'grpc.keepalive_time_ms': 120000, + 'grpc.http2.min_time_between_pings_ms': 120000, + 'grpc.keepalive_timeout_ms': 20000, + 'grpc.http2.max_pings_without_data': 0, + 'grpc.keepalive_permit_without_calls': 1, + }); + } + + private createIdentity(identity: FabricIdentity): Identity { + return { + mspId: identity.mspId, + credentials: Buffer.from(identity.certificate), + }; + } + + private createSigner(identity: FabricIdentity): Signer { + const privateKey = crypto.createPrivateKey(identity.privateKey); + return signers.newPrivateKeySigner(privateKey); + } + + public async initLedger(identityLabel: string): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Submit Transaction: InitLedger (${identityLabel})`); + await contract.submitTransaction('InitLedger'); + console.log('*** InitLedger transaction committed successfully'); + } + + + public async getAllRecords(identityLabel: string): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Evaluate Transaction: GetAllRecords (${identityLabel})`); const resultBytes = await contract.evaluateTransaction('GetAllRecords'); const resultJson = this.utf8Decoder.decode(resultBytes); return JSON.parse(resultJson) as MedicalRecord[]; } - public async addRecord(payload: MedicalRecord): Promise { - const contract = this.ensureContract(); - console.log('\n--> Submit Transaction: AddRecord'); + public async addRecord(identityLabel: string, payload: MedicalRecord): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Submit Transaction: AddRecord (${identityLabel})`); await contract.submitTransaction( 'AddRecord', payload.patientId, @@ -74,21 +127,26 @@ class FabricService { payload.gender, payload.bloodType, payload.ipfsCid, - payload.summary || '', + '', ); } - public async getRecordByPatientId(patientId: string): Promise { - const contract = this.ensureContract(); - console.log('\n--> Evaluate Transaction: GetRecord'); + public async getRecordByPatientId(identityLabel: string, patientId: string): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Evaluate Transaction: GetRecord (${identityLabel})`); const resultBytes = await contract.evaluateTransaction('GetRecord', patientId); const resultJson = this.utf8Decoder.decode(resultBytes); return JSON.parse(resultJson) as MedicalRecord; } - public async updateRecord(patientId: string, payload: Omit): Promise { - const contract = this.ensureContract(); - console.log('\n--> Submit Transaction: UpdateRecord'); + + public async updateRecord( + identityLabel: string, + patientId: string, + payload: Omit + ): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Submit Transaction: UpdateRecord (${identityLabel})`); await contract.submitTransaction( 'UpdateRecord', patientId, @@ -98,56 +156,60 @@ class FabricService { payload.gender, payload.bloodType, payload.ipfsCid, - payload.summary || '', + '', ); } - private async initLedger(): Promise { - const contract = this.ensureContract(); - console.log('\n--> Submit Transaction: InitLedger'); - await contract.submitTransaction('InitLedger'); - console.log('*** InitLedger transaction committed successfully'); + public async closeConnection(identityLabel: string): Promise { + const connection = this.connections.get(identityLabel); + if (connection) { + connection.gateway.close(); + connection.client.close(); + this.connections.delete(identityLabel); + console.log(`Closed connection for: ${identityLabel}`); + } } - private ensureContract(): Contract { - if (!this.contract) { - throw new HttpException(503, 'Fabric network connection is not ready'); + public closeAllConnections(): void { + for (const [label, connection] of this.connections) { + connection.gateway.close(); + connection.client.close(); + console.log(`Closed connection for: ${label}`); } - return this.contract; - } + this.connections.clear(); - private async newGrpcConnection(): Promise { - const tlsRootCert = await fs.readFile(this.tlsCertPath); - const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); - return new grpc.Client(this.peerEndpoint, tlsCredentials, { - 'grpc.ssl_target_name_override': this.peerHostAlias, - }); + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = null; + } } - private async newIdentity(): Promise { - const certPath = await this.getFirstDirFileName(this.certDirectoryPath); - const credentials = await fs.readFile(certPath); - return { mspId: this.mspId, credentials }; - } - private async newSigner(): Promise { - const keyPath = await this.getFirstDirFileName(this.keyDirectoryPath); - const privateKeyPem = await fs.readFile(keyPath); - const privateKey = crypto.createPrivateKey(privateKeyPem); - return signers.newPrivateKeySigner(privateKey); + private startCleanupInterval(): void { + this.cleanupInterval = setInterval(() => { + const now = new Date().getTime(); + + for (const [label, connection] of this.connections) { + const age = now - connection.lastUsed.getTime(); + if (age > this.CONNECTION_TTL_MS) { + connection.gateway.close(); + connection.client.close(); + this.connections.delete(label); + console.log(`Cleaned up stale connection for: ${label}`); + } + } + }, 5 * 60 * 1000); // Check every 5 minutes } - private async getFirstDirFileName(dirPath: string): Promise { - const files = await fs.readdir(dirPath); - if (!files[0]) { - throw new Error(`No files in directory: ${dirPath}`); - } - return path.join(dirPath, files[0]); - } - public close(): void { - this.gateway?.close(); - this.client?.close(); + public getConnectionStats(): { total: number; connections: Array<{ label: string; lastUsed: string }> } { + return { + total: this.connections.size, + connections: Array.from(this.connections.entries()).map(([label, conn]) => ({ + label, + lastUsed: conn.lastUsed.toISOString(), + })), + }; } } diff --git a/src/services/identity-storage.service.ts b/src/services/identity-storage.service.ts new file mode 100644 index 0000000..1de36ec --- /dev/null +++ b/src/services/identity-storage.service.ts @@ -0,0 +1,215 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import { FabricIdentity, FabricIdentityInput } from '@/interfaces/fabric-identity.interface'; +import { HttpException } from '@/exceptions/HttpException'; + +class IdentityStorageService { + private readonly storagePath: string; + private readonly encryptionKey: Buffer; + private identities: Map = new Map(); + private initialized: boolean = false; + + constructor() { + this.storagePath = process.env.FABRIC_IDENTITY_STORAGE_PATH || + path.resolve(__dirname, '../../data/fabric-identities.json'); + + // for private key encryption + const keyEnv = process.env.FABRIC_IDENTITY_ENCRYPTION_KEY; + if (keyEnv) { + this.encryptionKey = Buffer.from(keyEnv, 'hex'); + } else { + this.encryptionKey = crypto.scryptSync('development-only-key', 'salt', 32); + } + } + + public async initialize(): Promise { + if (this.initialized) return; + + try { + const dir = path.dirname(this.storagePath); + await fs.mkdir(dir, { recursive: true }); + + const data = await fs.readFile(this.storagePath, 'utf-8'); + const stored = JSON.parse(data) as FabricIdentity[]; + + for (const identity of stored) { + identity.privateKey = this.decrypt(identity.privateKey); + this.identities.set(identity.label, identity); + } + + console.log(`Loaded ${this.identities.size} Fabric identities from storage`); + } catch (error: any) { + if (error.code === 'ENOENT') { + console.log('No existing identity storage found. Starting fresh.'); + } else { + console.error('Error loading identity storage:', error.message); + } + } + + this.initialized = true; + } + + + public async storeIdentity(input: FabricIdentityInput): Promise { + await this.initialize(); + + const now = new Date().toISOString(); + const existing = this.identities.get(input.label); + + const identity: FabricIdentity = { + label: input.label, + mspId: input.mspId, + certificate: input.certificate, + privateKey: input.privateKey, + peerEndpoint: input.peerEndpoint, + peerHostAlias: input.peerHostAlias, + tlsCertificate: input.tlsCertificate, + channelName: input.channelName || 'mychannel', + chaincodeName: input.chaincodeName || 'emr', + createdAt: existing?.createdAt || now, + updatedAt: now, + }; + + + this.validateIdentity(identity); + + this.identities.set(identity.label, identity); + await this.persistToStorage(); + + console.log(`✅ Stored identity: ${identity.label} (MSP: ${identity.mspId})`); + + + return this.sanitizeIdentity(identity); + } + + + public async getIdentity(label: string): Promise { + await this.initialize(); + + const identity = this.identities.get(label); + if (!identity) { + throw new HttpException(404, `Identity not found: ${label}`); + } + + return identity; + } + + public async listIdentities(): Promise>> { + await this.initialize(); + + return Array.from(this.identities.values()).map(identity => ({ + label: identity.label, + mspId: identity.mspId, + peerEndpoint: identity.peerEndpoint, + peerHostAlias: identity.peerHostAlias, + channelName: identity.channelName, + chaincodeName: identity.chaincodeName, + createdAt: identity.createdAt, + updatedAt: identity.updatedAt, + })); + } + + public async deleteIdentity(label: string): Promise { + await this.initialize(); + + if (!this.identities.has(label)) { + throw new HttpException(404, `Identity not found: ${label}`); + } + + this.identities.delete(label); + await this.persistToStorage(); + + console.log(`🗑️ Deleted identity: ${label}`); + } + + public async hasIdentity(label: string): Promise { + await this.initialize(); + return this.identities.has(label); + } + + + private validateIdentity(identity: FabricIdentity): void { + if (!identity.label || identity.label.trim() === '') { + throw new HttpException(400, 'Identity label is required'); + } + + if (!identity.mspId || identity.mspId.trim() === '') { + throw new HttpException(400, 'MSP ID is required'); + } + + if (!identity.certificate || !identity.certificate.includes('BEGIN CERTIFICATE')) { + throw new HttpException(400, 'Invalid certificate PEM format'); + } + + if (!identity.privateKey || !identity.privateKey.includes('BEGIN')) { + throw new HttpException(400, 'Invalid private key PEM format'); + } + + if (!identity.peerEndpoint || !identity.peerEndpoint.includes(':')) { + throw new HttpException(400, 'Invalid peer endpoint format (expected host:port)'); + } + + if (!identity.tlsCertificate || !identity.tlsCertificate.includes('BEGIN CERTIFICATE')) { + throw new HttpException(400, 'Invalid TLS certificate PEM format'); + } + } + + + private async persistToStorage(): Promise { + const toStore = Array.from(this.identities.values()).map(identity => ({ + ...identity, + + privateKey: this.encrypt(identity.privateKey), + })); + + await fs.writeFile( + this.storagePath, + JSON.stringify(toStore, null, 2), + { mode: 0o600 } // Read/write only for owner + ); + } + + private encrypt(plaintext: string): string { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv); + + let encrypted = cipher.update(plaintext, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + const authTag = cipher.getAuthTag(); + + + return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`; + } + + + private decrypt(ciphertext: string): string { + if (!ciphertext.includes(':')) { + // Data is not encrypted + return ciphertext; + } + + const [ivHex, authTagHex, encrypted] = ciphertext.split(':'); + + const iv = Buffer.from(ivHex, 'hex'); + const authTag = Buffer.from(authTagHex, 'hex'); + + const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + } + + private sanitizeIdentity(identity: FabricIdentity): FabricIdentity { + return { + ...identity, + privateKey: '[REDACTED]', + }; + } +} + +export default new IdentityStorageService(); \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index ff85374..eb9e93c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -27,6 +27,41 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User signup data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "password": { + "type": "string", + "example": "password123" + } + }, + "required": [ + "email", + "name", + "phone", + "password" + ] + } + } + ], "responses": { "default": { "description": "" @@ -40,6 +75,35 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "user@example.com" + }, + "password": { + "type": "string", + "example": "password123" + }, + "rememberMe": { + "type": "boolean", + "example": false + } + }, + "required": [ + "emailOrUsername", + "password" + ] + } + } + ], "responses": { "default": { "description": "" @@ -79,6 +143,31 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Complete user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "gender": { + "type": "string", + "example": "Male" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + } + }, + "required": [ + "gender", + "date_of_birth" + ] + } + } + ], "responses": { "default": { "description": "" @@ -92,6 +181,26 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Verify OTP", + "required": true, + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "example": "123456" + } + }, + "required": [ + "otp" + ] + } + } + ], "responses": { "default": { "description": "" @@ -105,6 +214,26 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Request password reset", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + } + }, + "required": [ + "email" + ] + } + } + ], "responses": { "default": { "description": "" @@ -118,6 +247,31 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Reset password", + "required": true, + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "reset-token" + }, + "newPassword": { + "type": "string", + "example": "newPassword123" + } + }, + "required": [ + "token", + "newPassword" + ] + } + } + ], "responses": { "default": { "description": "" @@ -170,6 +324,26 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Update Google user phone", + "required": true, + "schema": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "example": "1234567890" + } + }, + "required": [ + "phone" + ] + } + } + ], "responses": { "default": { "description": "" @@ -190,12 +364,161 @@ } } }, + "/fabric/onboard": { + "post": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Identity onboarding data", + "required": true, + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "example": "org1" + }, + "mspId": { + "type": "string", + "example": "Org1MSP" + }, + "certificate": { + "type": "string", + "example": "PEM certificate" + }, + "privateKey": { + "type": "string", + "example": "PEM private key" + }, + "peerEndpoint": { + "type": "string", + "example": "localhost:7051" + }, + "peerHostAlias": { + "type": "string", + "example": "peer0.org1.example.com" + }, + "tlsCertificate": { + "type": "string", + "example": "PEM TLS certificate" + }, + "channelName": { + "type": "string", + "example": "mychannel" + }, + "chaincodeName": { + "type": "string", + "example": "basic" + } + }, + "required": [ + "label", + "mspId", + "certificate", + "privateKey", + "peerEndpoint", + "peerHostAlias", + "tlsCertificate" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/identities": { + "get": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/identities/{label}": { + "delete": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "label", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/connections": { + "get": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/init-ledger": { + "post": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, "/records": { "get": { "tags": [ "MedicalRecords" ], "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -207,6 +530,67 @@ "MedicalRecords" ], "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Medical record data", + "required": true, + "schema": { + "type": "object", + "properties": { + "patientId": { + "type": "string", + "example": "P12345" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "dateOfBirth": { + "type": "string", + "example": "1990-01-01" + }, + "gender": { + "type": "string", + "example": "Male" + }, + "bloodType": { + "type": "string", + "example": "O+" + }, + "ipfsCid": { + "type": "string", + "example": "Qm..." + }, + "summary": { + "type": "string", + "example": "Optional summary" + } + }, + "required": [ + "patientId", + "firstName", + "lastName", + "dateOfBirth", + "gender", + "bloodType", + "ipfsCid" + ] + } + } + ], "responses": { "default": { "description": "" @@ -239,6 +623,13 @@ "in": "path", "required": true, "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" } ], "responses": { @@ -258,6 +649,60 @@ "in": "path", "required": true, "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Update medical record data", + "required": true, + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "dateOfBirth": { + "type": "string", + "example": "1990-01-01" + }, + "gender": { + "type": "string", + "example": "Male" + }, + "bloodType": { + "type": "string", + "example": "O+" + }, + "ipfsCid": { + "type": "string", + "example": "Qm..." + }, + "summary": { + "type": "string", + "example": "Optional summary" + } + }, + "required": [ + "firstName", + "lastName", + "dateOfBirth", + "gender", + "bloodType", + "ipfsCid" + ] + } } ], "responses": { diff --git a/src/swagger.js b/src/swagger.js index 6ea1337..be54642 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,6 +14,6 @@ const doc = { }; const outputFile = './swagger-output.json'; -const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts']; +const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From 33d99c7d6628dcf730883cb1377eb9b5ff613be0 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 12 Dec 2025 15:45:31 +0200 Subject: [PATCH 043/210] feat: super admin and admin routes and initial controls, added also multi language support --- .gitignore | 4 + .swcrc | 4 +- notes.txt | 35 ++ package-lock.json | 312 +++++++++++++----- package.json | 9 +- src/constants/specializations.ts | 158 +++++++++ src/controllers/admin.controller.ts | 75 +++++ src/controllers/superAdmin.controller.ts | 18 + src/dtos/admins.dto.ts | 29 ++ src/dtos/superAdmins.dto.ts | 28 ++ src/middlewares/auth.middleware.ts | 21 +- src/middlewares/language.middleware.ts | 31 ++ .../migration.sql | 18 + src/prisma/schema.prisma | 29 +- src/routes/admin.route.ts | 48 +++ src/routes/superAdmin.route.ts | 27 ++ src/server.ts | 4 +- src/services/admin.service.ts | 100 ++++++ src/services/auth.service.ts | 5 +- src/services/superAdmin.service.ts | 48 +++ src/utils/specializationTransform.ts | 52 +++ src/validators/specialization.validator.ts | 40 +++ tsconfig.json | 4 +- 23 files changed, 1000 insertions(+), 99 deletions(-) create mode 100644 notes.txt create mode 100644 src/constants/specializations.ts create mode 100644 src/controllers/admin.controller.ts create mode 100644 src/controllers/superAdmin.controller.ts create mode 100644 src/dtos/admins.dto.ts create mode 100644 src/dtos/superAdmins.dto.ts create mode 100644 src/middlewares/language.middleware.ts create mode 100644 src/prisma/migrations/20251212114202_added_doctor_info/migration.sql create mode 100644 src/routes/admin.route.ts create mode 100644 src/routes/superAdmin.route.ts create mode 100644 src/services/admin.service.ts create mode 100644 src/services/superAdmin.service.ts create mode 100644 src/utils/specializationTransform.ts create mode 100644 src/validators/specialization.validator.ts diff --git a/.gitignore b/.gitignore index 0ccb8df..8526d41 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,7 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +# Temporary folders +docker-compose-local.yml +docs \ No newline at end of file diff --git a/.swcrc b/.swcrc index 070d681..c56acb7 100644 --- a/.swcrc +++ b/.swcrc @@ -29,7 +29,9 @@ "@middlewares/*": ["middlewares/*"], "@routes/*": ["routes/*"], "@services/*": ["services/*"], - "@utils/*": ["utils/*"] + "@utils/*": ["utils/*"], + "@constants/*": ["constants/*"], + "@validators/*": ["validators/*"] } }, "module": { diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..ec5fd8b --- /dev/null +++ b/notes.txt @@ -0,0 +1,35 @@ +const prisma = new PrismaClient(); + +export default prisma; +prisma.service.ts +prisma.TABLENAME + + +catchAsync--> to reduce try catch + +name of db fields to be snake Case + +Handle Image for user as general + +JIRA + +Admin: +- Create doctor +- Create Clinic +- Get all existing clinics to see if to link directly +- Link doctor to Clinic + +doctor: +- first time login to change password that is default +- according to clinic id , make the doctor schedule + +- Patient: +- Get all doctors with their clinic details respectively +- get the doctors schedule that is free and reserved +- each patient select one reservation per day +- Look for cancellation policy + +Clinic: +add clinic name +add location link as google maps + diff --git a/package-lock.json b/package-lock.json index 463d611..92a68af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,11 @@ "dependencies": { "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", - "@prisma/client": "^6.18.0", + "@prisma/client": "6.18.0", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", + "cloudinary": "^2.8.0", "compression": "^1.8.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -26,9 +27,11 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "multer": "^2.0.2", "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", @@ -71,7 +74,6 @@ "nodemon": "^3.1.10", "pm2": "^6.0.13", "prettier": "^3.6.2", - "prisma": "^6.18.0", "supertest": "^7.1.4", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", @@ -2449,8 +2451,9 @@ }, "node_modules/@prisma/client": { "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -2469,8 +2472,8 @@ }, "node_modules/@prisma/config": { "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", @@ -2480,14 +2483,14 @@ }, "node_modules/@prisma/debug": { "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==" }, "node_modules/@prisma/engines": { "version": "6.18.0", - "devOptional": true, + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", @@ -2497,13 +2500,13 @@ }, "node_modules/@prisma/engines-version": { "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "devOptional": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==" }, "node_modules/@prisma/fetch-engine": { "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", @@ -2512,8 +2515,8 @@ }, "node_modules/@prisma/get-platform": { "version": "6.18.0", - "devOptional": true, - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", "dependencies": { "@prisma/debug": "6.18.0" } @@ -3209,8 +3212,8 @@ }, "node_modules/@standard-schema/spec": { "version": "1.0.0", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" }, "node_modules/@swc/cli": { "version": "0.7.8", @@ -4273,6 +4276,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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==", + "license": "MIT" + }, "node_modules/arch": { "version": "3.0.0", "dev": true, @@ -4724,9 +4733,19 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, "license": "MIT" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -4736,8 +4755,8 @@ }, "node_modules/c12": { "version": "3.1.0", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", @@ -4763,8 +4782,8 @@ }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", - "devOptional": true, - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "engines": { "node": ">=12" }, @@ -4920,7 +4939,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -4956,8 +4974,8 @@ }, "node_modules/citty": { "version": "0.1.6", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", "dependencies": { "consola": "^3.2.3" } @@ -5113,6 +5131,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/cloudinary": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz", + "integrity": "sha512-s7frvR0HnQXeJsQSIsbLa/I09IMb1lOnVLEDH5b5E53WTiCYgrNNOBGV/i/nLHwrcEOUkqjfSwP1+enXWNYmdw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "q": "^1.5.1" + }, + "engines": { + "node": ">=9" + } + }, "node_modules/co": { "version": "4.6.0", "dev": true, @@ -5259,15 +5290,30 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/confbox": { "version": "0.2.2", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==" }, "node_modules/consola": { "version": "3.4.2", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -5457,8 +5503,8 @@ }, "node_modules/deepmerge-ts": { "version": "7.1.5", - "devOptional": true, - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", "engines": { "node": ">=16.0.0" } @@ -5484,8 +5530,8 @@ }, "node_modules/defu": { "version": "6.1.4", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" }, "node_modules/degenerator": { "version": "5.0.1", @@ -5517,8 +5563,8 @@ }, "node_modules/destr": { "version": "2.0.5", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==" }, "node_modules/detect-newline": { "version": "3.1.0", @@ -5648,8 +5694,8 @@ }, "node_modules/effect": { "version": "3.18.4", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" @@ -5678,8 +5724,8 @@ }, "node_modules/empathic": { "version": "2.0.0", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", "engines": { "node": ">=14" } @@ -6258,9 +6304,9 @@ } }, "node_modules/exsolve": { - "version": "1.0.7", - "devOptional": true, - "license": "MIT" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==" }, "node_modules/ext-list": { "version": "2.2.2", @@ -6295,7 +6341,8 @@ }, "node_modules/fast-check": { "version": "3.23.2", - "devOptional": true, + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", "funding": [ { "type": "individual", @@ -6306,7 +6353,6 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", "dependencies": { "pure-rand": "^6.1.0" }, @@ -6316,7 +6362,8 @@ }, "node_modules/fast-check/node_modules/pure-rand": { "version": "6.1.0", - "devOptional": true, + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "funding": [ { "type": "individual", @@ -6326,8 +6373,7 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "license": "MIT" + ] }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -6848,8 +6894,8 @@ }, "node_modules/giget": { "version": "2.0.0", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -8040,7 +8086,6 @@ }, "node_modules/jiti": { "version": "2.6.1", - "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8151,10 +8196,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -8696,7 +8743,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8891,6 +8937,79 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/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/multer/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/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/mute-stream": { "version": "0.0.8", "dev": true, @@ -9009,8 +9128,8 @@ }, "node_modules/node-fetch-native": { "version": "1.6.7", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==" }, "node_modules/node-gyp": { "version": "11.5.0", @@ -9077,9 +9196,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", - "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", "engines": { "node": ">=6.0.0" } @@ -9260,8 +9379,8 @@ }, "node_modules/nypm": { "version": "0.6.2", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", @@ -9307,8 +9426,8 @@ }, "node_modules/ohash": { "version": "2.0.11", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==" }, "node_modules/on-finished": { "version": "2.4.1", @@ -9629,8 +9748,8 @@ }, "node_modules/pathe": { "version": "2.0.3", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" }, "node_modules/pause": { "version": "0.0.1", @@ -9644,8 +9763,8 @@ }, "node_modules/perfect-debounce": { "version": "1.0.0", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" }, "node_modules/picocolors": { "version": "1.1.1", @@ -9779,8 +9898,8 @@ }, "node_modules/pkg-types": { "version": "2.3.0", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", @@ -10074,9 +10193,9 @@ }, "node_modules/prisma": { "version": "6.18.0", - "devOptional": true, + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" @@ -10218,6 +10337,17 @@ ], "license": "MIT" }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.14.0", "license": "BSD-3-Clause", @@ -10313,8 +10443,8 @@ }, "node_modules/rc9": { "version": "2.1.2", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -10352,7 +10482,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -10971,6 +11100,14 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.23.0", "dev": true, @@ -11549,9 +11686,12 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.1", - "devOptional": true, - "license": "MIT" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -11923,6 +12063,12 @@ "node": ">= 0.6" } }, + "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==", + "license": "MIT" + }, "node_modules/typedi": { "version": "0.10.0", "license": "MIT" @@ -12126,8 +12272,9 @@ } }, "node_modules/validator": { - "version": "13.15.20", - "license": "MIT", + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", "engines": { "node": ">= 0.10" } @@ -12380,6 +12527,15 @@ } } }, + "node_modules/xtend": { + "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" + } + }, "node_modules/y18n": { "version": "5.0.8", "license": "ISC", diff --git a/package.json b/package.json index fa9ede0..b1d3ca4 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "lint": "eslint --ignore-path .gitignore --ext .ts src/", "lint:fix": "npm run lint -- --fix", "prisma:init": "prisma init", - "prisma:migrate": "prisma migrate dev --preview-feature", + "prisma:migrate": "dotenv -e .env.development.local -- npx prisma migrate dev --schema=src/prisma/schema.prisma", "prisma:generate": "prisma generate", + "prisma:studio": "dotenv -e .env.development.local -- prisma studio", "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", "deploy:dev": "pm2 start ecosystem.config.js --only dev" }, @@ -24,10 +25,11 @@ "dependencies": { "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", - "@prisma/client": "^6.18.0", + "@prisma/client": "6.18.0", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", + "cloudinary": "^2.8.0", "compression": "^1.8.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -39,9 +41,11 @@ "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", + "multer": "^2.0.2", "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", @@ -84,7 +88,6 @@ "nodemon": "^3.1.10", "pm2": "^6.0.13", "prettier": "^3.6.2", - "prisma": "^6.18.0", "supertest": "^7.1.4", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", diff --git a/src/constants/specializations.ts b/src/constants/specializations.ts new file mode 100644 index 0000000..d7f4e89 --- /dev/null +++ b/src/constants/specializations.ts @@ -0,0 +1,158 @@ +export const SPECIALIZATIONS = { + CARDIOLOGY: { + en: 'Cardiology', + ar: 'أمراض القلب', + }, + DERMATOLOGY: { + en: 'Dermatology', + ar: 'الأمراض الجلدية', + }, + ENDOCRINOLOGY: { + en: 'Endocrinology', + ar: 'الغدد الصماء', + }, + GASTROENTEROLOGY: { + en: 'Gastroenterology', + ar: 'الجهاز الهضمي', + }, + GENERAL_PRACTICE: { + en: 'General Practice', + ar: 'الطب العام', + }, + GYNECOLOGY: { + en: 'Gynecology', + ar: 'أمراض النساء', + }, + HEMATOLOGY: { + en: 'Hematology', + ar: 'أمراض الدم', + }, + INTERNAL_MEDICINE: { + en: 'Internal Medicine', + ar: 'الباطنية', + }, + NEPHROLOGY: { + en: 'Nephrology', + ar: 'أمراض الكلى', + }, + NEUROLOGY: { + en: 'Neurology', + ar: 'الأمراض العصبية', + }, + NEUROSURGERY: { + en: 'Neurosurgery', + ar: 'جراحة المخ والأعصاب', + }, + OBSTETRICS: { + en: 'Obstetrics', + ar: 'التوليد', + }, + ONCOLOGY: { + en: 'Oncology', + ar: 'الأورام', + }, + OPHTHALMOLOGY: { + en: 'Ophthalmology', + ar: 'طب العيون', + }, + ORTHOPEDICS: { + en: 'Orthopedics', + ar: 'جراحة العظام', + }, + OTOLARYNGOLOGY: { + en: 'Otolaryngology (ENT)', + ar: 'الأنف والأذن والحنجرة', + }, + PEDIATRICS: { + en: 'Pediatrics', + ar: 'طب الأطفال', + }, + PSYCHIATRY: { + en: 'Psychiatry', + ar: 'الطب النفسي', + }, + PULMONOLOGY: { + en: 'Pulmonology', + ar: 'أمراض الصدر', + }, + RADIOLOGY: { + en: 'Radiology', + ar: 'الأشعة', + }, + RHEUMATOLOGY: { + en: 'Rheumatology', + ar: 'أمراض الروماتيزم', + }, + SURGERY: { + en: 'General Surgery', + ar: 'الجراحة العامة', + }, + UROLOGY: { + en: 'Urology', + ar: 'المسالك البولية', + }, + ANESTHESIOLOGY: { + en: 'Anesthesiology', + ar: 'التخدير', + }, + EMERGENCY_MEDICINE: { + en: 'Emergency Medicine', + ar: 'طب الطوارئ', + }, + FAMILY_MEDICINE: { + en: 'Family Medicine', + ar: 'طب الأسرة', + }, + PATHOLOGY: { + en: 'Pathology', + ar: 'علم الأمراض', + }, + PHYSICAL_THERAPY: { + en: 'Physical Therapy', + ar: 'العلاج الطبيعي', + }, + PLASTIC_SURGERY: { + en: 'Plastic Surgery', + ar: 'جراحة التجميل', + }, + SPORTS_MEDICINE: { + en: 'Sports Medicine', + ar: 'طب الرياضة', + }, +} as const; + +// Type for specialization keys +export type SpecializationKey = keyof typeof SPECIALIZATIONS; + +// Get all valid specialization keys +export const VALID_SPECIALIZATION_KEYS = Object.keys(SPECIALIZATIONS) as SpecializationKey[]; + +// Get all English specialization values +export const VALID_SPECIALIZATIONS_EN = Object.values(SPECIALIZATIONS).map(spec => spec.en); + +// Type for specialization english values +export type SpecializationEnglishValue = (typeof SPECIALIZATIONS)[keyof typeof SPECIALIZATIONS]['en']; + +// Get all Arabic specialization values +export const VALID_SPECIALIZATIONS_AR = Object.values(SPECIALIZATIONS).map(spec => spec.ar); + +// Type for specialization arabic values +export type SpecializationArabicValue = (typeof SPECIALIZATIONS)[keyof typeof SPECIALIZATIONS]['ar']; + +// Helper function to get specialization by key +export const getSpecialization = (key: SpecializationKey) => { + return SPECIALIZATIONS[key]; +}; + +// Helper function to validate specialization +export const isValidSpecialization = (value: string): boolean => { + return VALID_SPECIALIZATION_KEYS.includes(value as SpecializationKey); +}; + +// Helper function to get specialization key from English or Arabic value +export const getSpecializationKey = (value: string): SpecializationKey | null => { + const entry = Object.entries(SPECIALIZATIONS).find( + ([_, spec]) => spec.en === value || spec.ar === value + ); + return entry ? (entry[0] as SpecializationKey) : null; +}; diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts new file mode 100644 index 0000000..4c4db57 --- /dev/null +++ b/src/controllers/admin.controller.ts @@ -0,0 +1,75 @@ + +import { NextFunction, Request, Response } from 'express'; +import { Container } from 'typedi'; +import { AdminService } from '@/services/admin.service'; +import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { RequestWithLanguage } from '@/middlewares/language.middleware'; +import { formatSpecializationResponse } from '@/utils/specializationTransform'; +import { SpecializationKey } from '@/constants/specializations'; + +export class AdminController { + public adminService = Container.get(AdminService); + + public addDoctor = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + try { + const doctorData: AddDoctorFromAdminDto = req.body; + const newDoctor = await this.adminService.addDoctor(doctorData); + + res.status(201).json({ + data: newDoctor, + message: 'Doctor added successfully' + }); + } catch (error) { + next(error); + } + }; + + public getAllDoctors = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const doctors = await this.adminService.getAllDoctors(); + const language = req.language; + + // Format specializations based on language preference + const formattedDoctors = doctors.map(doctor => ({ + ...doctor, + doctor: doctor.doctor ? { + ...doctor.doctor, + specialization: formatSpecializationResponse( + doctor.doctor.specialization as SpecializationKey, + language + ), + } : null, + })); + + res.status(200).json({ + data: formattedDoctors, + message: 'Doctors retrieved successfully' + }); + } + + + public getDoctorById = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const doctorId = req.params.id; + const doctor = await this.adminService.getDoctorById(doctorId); + const language = req.language; + + // Format specialization based on language preference + const formattedDoctor = { + ...doctor, + doctor: doctor.doctor ? { + ...doctor.doctor, + specialization: formatSpecializationResponse( + doctor.doctor.specialization as SpecializationKey, + language + ), + } : null, + }; + + res.status(200).json({ + data: formattedDoctor, + message: 'Doctor retrieved successfully' + }); + } + +} \ No newline at end of file diff --git a/src/controllers/superAdmin.controller.ts b/src/controllers/superAdmin.controller.ts new file mode 100644 index 0000000..94edbfe --- /dev/null +++ b/src/controllers/superAdmin.controller.ts @@ -0,0 +1,18 @@ +import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; +import { SuperAdminService } from "@/services/superAdmin.service"; +import { NextFunction, Response, Request } from "express"; +import Container from "typedi"; + +export class SuperAdminController { + + public superAdminService = Container.get(SuperAdminService); + + public addAdmin = async (req: Request, res: Response, next: NextFunction): Promise => { + const adminData: AddAdminFromSuperAdminDto = req.body; + const newAdmin = await this.superAdminService.addAdmin(adminData); + res.status(201).json({ + data: newAdmin, + message: 'Admin added successfully' + }); + } +} \ No newline at end of file diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts new file mode 100644 index 0000000..2f9aa41 --- /dev/null +++ b/src/dtos/admins.dto.ts @@ -0,0 +1,29 @@ +import { Gender } from "@prisma/client"; +import { IsEmail, IsNotEmpty, IsString } from "class-validator"; +import { IsValidSpecialization } from "@/validators/specialization.validator"; +import { TransformSpecialization } from "@/utils/specializationTransform"; + +export class AddDoctorFromAdminDto { + @IsEmail() + @IsNotEmpty() + public email: string; + + @IsString() + @IsNotEmpty() + public name: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsString() + public gender: Gender; + + @IsString() + @IsNotEmpty() + @TransformSpecialization() // Converts EN/AR to key before validation + @IsValidSpecialization({ + message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' + }) + public specialization: string; +} \ No newline at end of file diff --git a/src/dtos/superAdmins.dto.ts b/src/dtos/superAdmins.dto.ts new file mode 100644 index 0000000..74a722b --- /dev/null +++ b/src/dtos/superAdmins.dto.ts @@ -0,0 +1,28 @@ +import { Gender } from "@prisma/client"; +import { IsEmail, IsNotEmpty, IsString } from "class-validator"; + +export class AddAdminFromSuperAdminDto { + @IsEmail() + @IsNotEmpty() + public email: string; + + @IsString() + @IsNotEmpty() + public name: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsString() + @IsNotEmpty() + public password: string; + + @IsString() + @IsNotEmpty() + public gender: Gender; + + @IsString() + @IsNotEmpty() + public date_of_birth: string; +} \ No newline at end of file diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index c9ccf9b..e53ef7f 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient, Role } from '@prisma/client'; import { NextFunction, Response, Request } from 'express'; import { verify } from 'jsonwebtoken'; import { SECRET_KEY } from '@config'; @@ -42,3 +42,22 @@ export const AuthMiddleware = async (req: RequestWithUser, res: Response, next: next(new HttpException(err.status, err.message, err.messageAr)); } }; + +export const RoleMiddleware = (...allowedRoles: Role[]) => { + return (req: RequestWithUser, res: Response, next: NextFunction) => { + if (!req.user) { + const error = createBilingualError(401, ErrorMessages.AUTHENTICATION_REQUIRED); + return next(new HttpException(error.status, error.message, error.messageAr)); + } + + if (!allowedRoles.includes(req.user.role)) { + const error = createBilingualError(403, { + en: 'Access denied. Insufficient permissions.', + ar: 'تم رفض الوصول. صلاحيات غير كافية.' + }); + return next(new HttpException(error.status, error.message, error.messageAr)); + } + + next(); + }; +}; diff --git a/src/middlewares/language.middleware.ts b/src/middlewares/language.middleware.ts new file mode 100644 index 0000000..155eca6 --- /dev/null +++ b/src/middlewares/language.middleware.ts @@ -0,0 +1,31 @@ +import { NextFunction, Request, Response } from 'express'; + +export interface RequestWithLanguage extends Request { + language?: 'en' | 'ar'; +} + +/** + * Middleware to extract language preference from request headers + * Checks for 'Accept-Language' or custom 'X-Language' header + */ +export const LanguageMiddleware = (req: RequestWithLanguage, res: Response, next: NextFunction) => { + // Check custom header first + const customLang = req.header('X-Language')?.toLowerCase(); + + if (customLang === 'ar' || customLang === 'arabic') { + req.language = 'ar'; + } else if (customLang === 'en' || customLang === 'english') { + req.language = 'en'; + } else { + // Check Accept-Language header + const acceptLang = req.header('Accept-Language')?.toLowerCase(); + + if (acceptLang?.includes('ar')) { + req.language = 'ar'; + } else { + req.language = 'en'; // Default to English + } + } + + next(); +}; diff --git a/src/prisma/migrations/20251212114202_added_doctor_info/migration.sql b/src/prisma/migrations/20251212114202_added_doctor_info/migration.sql new file mode 100644 index 0000000..b44e870 --- /dev/null +++ b/src/prisma/migrations/20251212114202_added_doctor_info/migration.sql @@ -0,0 +1,18 @@ +/* + Warnings: + + - Added the required column `phone` to the `Clinic` table without a default value. This is not possible if the table is not empty. + - Added the required column `fees` to the `ClinicDoctor` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Clinic" ADD COLUMN "address_maps_link" VARCHAR(500), +ADD COLUMN "canPayOnline" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "phone" VARCHAR(20) NOT NULL; + +-- AlterTable +ALTER TABLE "ClinicDoctor" ADD COLUMN "fees" DOUBLE PRECISION NOT NULL; + +-- AlterTable +ALTER TABLE "Users" ADD COLUMN "photo_public_id" VARCHAR(500), +ADD COLUMN "photo_url" VARCHAR(500); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index e2bc5c7..f01aa0f 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -26,6 +26,8 @@ model User { email_OTP_expires_at DateTime? password_reset_token String? @db.VarChar(255) password_reset_token_expires_at DateTime? + photo_url String? @db.VarChar(500) + photo_public_id String? @db.VarChar(500) created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? @@ -48,10 +50,9 @@ model User { } model Doctor { - id String @id @default(uuid()) - specialization String @db.VarChar(255) - avg_time DateTime? @db.Time(0) - + id String @id @default(uuid()) + specialization String @db.VarChar(255) + avg_time DateTime? @db.Time(0) // Relations user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) clinic_doctors ClinicDoctor[] @@ -143,14 +144,17 @@ model ScanLab { } model Clinic { - id String @id @default(uuid()) - is_active Boolean @default(true) - opening_at DateTime @db.Time(0) - closing_at DateTime @db.Time(0) - address String @db.VarChar(300) - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? + id String @id @default(uuid()) + is_active Boolean @default(true) + opening_at DateTime @db.Time(0) + closing_at DateTime @db.Time(0) + address String @db.VarChar(300) + address_maps_link String? @db.VarChar(500) + phone String @db.VarChar(20) + canPayOnline Boolean @default(false) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? // Relations clinic_nurses ClinicNurse[] @@ -177,6 +181,7 @@ model ClinicDoctor { id String @id @default(uuid()) clinic_id String doctor_id String + fees Float // Relations clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts new file mode 100644 index 0000000..9aef22e --- /dev/null +++ b/src/routes/admin.route.ts @@ -0,0 +1,48 @@ +import { Router } from 'express'; +import { AdminController } from '@/controllers/admin.controller'; +import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { Routes } from '@/interfaces'; +import { AuthMiddleware, RoleMiddleware } from '@/middlewares/auth.middleware'; +import { LanguageMiddleware } from '@/middlewares/language.middleware'; +import { ValidationMiddleware } from '@/middlewares/validation.middleware'; +import { Role } from '@prisma/client'; + +export class AdminRoute implements Routes { + public path = '/admin'; + public router = Router(); + public adminController = new AdminController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.post( + `${this.path}/doctors`, + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + LanguageMiddleware, + ValidationMiddleware(AddDoctorFromAdminDto), + this.adminController.addDoctor, + ); + + this.router.get( + `${this.path}/doctors`, + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + LanguageMiddleware, + this.adminController.getAllDoctors, + ); + + this.router.get( + `${this.path}/doctors/:id`, + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + LanguageMiddleware, + this.adminController.getDoctorById, + ); + } +} \ No newline at end of file diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts new file mode 100644 index 0000000..2973c1c --- /dev/null +++ b/src/routes/superAdmin.route.ts @@ -0,0 +1,27 @@ +import { SuperAdminController } from "@/controllers/superAdmin.controller"; +import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; +import { Routes } from "@/interfaces"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Router } from "express"; + + +export class SuperAdminRoute implements Routes { + public path = '/super-admin'; + public router = Router(); + public superAdminController = new SuperAdminController(); + + constructor() { + this.initializeRoutes(); + } + private initializeRoutes() { + this.router.post( + `${this.path}/admins`, + /* #swagger.tags = ['Super Admin'] */ + // AuthMiddleware, + // RoleMiddleware(Role.SUPER_ADMIN), + // LanguageMiddleware, + ValidationMiddleware(AddAdminFromSuperAdminDto), + this.superAdminController.addAdmin, + ); + } +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 2a10f95..f799f33 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,11 @@ import { App } from '@/app'; import { AuthRoute } from '@routes/auth.route'; +import { AdminRoute } from '@routes/admin.route'; import { ValidateEnv } from '@utils/validateEnv'; import { FabricRoute } from '@routes/fabric.route'; +import { SuperAdminRoute } from './routes/superAdmin.route'; ValidateEnv(); -const app = new App([new AuthRoute(), new FabricRoute()]); +const app = new App([new AuthRoute(), new FabricRoute(), new AdminRoute() , new SuperAdminRoute()]); app.listen(); diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts new file mode 100644 index 0000000..30ac7d4 --- /dev/null +++ b/src/services/admin.service.ts @@ -0,0 +1,100 @@ +import { PrismaClient, Role } from '@prisma/client'; +import { hash } from 'bcrypt'; +import { Service } from 'typedi'; +import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { HttpException } from '@/exceptions/HttpException'; +import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; +import { User } from '@/interfaces'; + +// TO BE CHANGED +const prisma = new PrismaClient(); + +@Service() +export class AdminService { + public async addDoctor(doctorData: AddDoctorFromAdminDto): Promise { + // Check if email already exists + const existingUser = await prisma.user.findUnique({ + where: { email: doctorData.email } + }); + + if (existingUser) { + const error = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // Generate username from email + const username = doctorData.email.split('@')[0]; + + // Check if username exists + const existingUsername = await prisma.user.findUnique({ + where: { username } + }); + + if (existingUsername) { + const error = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // Generate default password (doctor can change it later) + const defaultPassword = 'doctor123'; // Should be changed on first login + const hashedPassword = await hash(defaultPassword, 10); + + // Create user with doctor role + const createdUser = await prisma.user.create({ + data: { + email: doctorData.email, + name: doctorData.name, + username, + phone: doctorData.phone, + gender: doctorData.gender, + date_of_birth: new Date('1990-01-01'), + password_hash: hashedPassword, + role: Role.DOCTOR, + isVerified: true, + hasCompletedProfile: false, + } + }); + + // Create doctor profile + await prisma.doctor.create({ + data: { + id: createdUser.id, + specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") + } + }); + + return createdUser; + + } + + public async getAllDoctors() { + + const doctors = await prisma.user.findMany({ + where: { role: Role.DOCTOR }, + include: { + doctor: true, + }, + }); + + return doctors; + + } + + public async getDoctorById(id: string) { + + const doctor = await prisma.user.findUnique({ + where: { id, role: Role.DOCTOR }, + include: { + doctor: true, + }, + }); + + if (!doctor) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return doctor; + } + +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index ad5814b..9c93f59 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -13,6 +13,7 @@ import crypto from 'crypto'; @Service() export class AuthService { + // TO BE EDITED public users = new PrismaClient().user; public patients = new PrismaClient().patient; public refreshTokens = new PrismaClient().refreshToken; @@ -87,7 +88,7 @@ export class AuthService { isVerified, hasCompletedProfile }; - + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); @@ -287,7 +288,7 @@ export class AuthService { await transporter.sendMail(mailOptions); } - + public async getUserEmail(req: RequestWithUser): Promise { const email = await this.users.findUnique({ where: { id: req.user.id }, diff --git a/src/services/superAdmin.service.ts b/src/services/superAdmin.service.ts new file mode 100644 index 0000000..059c7a9 --- /dev/null +++ b/src/services/superAdmin.service.ts @@ -0,0 +1,48 @@ +import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; +import { PrismaClient, Role } from "@prisma/client"; +import { hash } from "bcrypt"; +import { Service } from "typedi"; + +const prisma = new PrismaClient(); + +@Service() +export class SuperAdminService { + + public async addAdmin(adminData: AddAdminFromSuperAdminDto): Promise { + // Logic to add a new admin + // Check if email already exists + const existingUser = await prisma.user.findUnique({ + where: { email: adminData.email } + }); + if (existingUser) { + throw new Error('Email already exists'); + } + const username = adminData.email.split('@')[0]; + + // Check if username exists + const existingUsername = await prisma.user.findUnique({ + where: { username } + }); + if (existingUsername) { + throw new Error('Username already exists'); + } + + const hashedPassword = await hash(adminData.password, 10); + + const newAdmin = await prisma.user.create({ + data: { + email: adminData.email, + name: adminData.name, + username: username, + password_hash: hashedPassword, + role: Role.ADMIN, + phone: adminData.phone, + gender: adminData.gender, + isVerified: true, + hasCompletedProfile: true, + date_of_birth: new Date(adminData.date_of_birth), + } + }); + return newAdmin; + } +} \ No newline at end of file diff --git a/src/utils/specializationTransform.ts b/src/utils/specializationTransform.ts new file mode 100644 index 0000000..ca2db81 --- /dev/null +++ b/src/utils/specializationTransform.ts @@ -0,0 +1,52 @@ +import { Transform } from 'class-transformer'; +import { getSpecializationKey, SpecializationKey } from '@/constants/specializations'; + +/** + * Transform decorator that converts English or Arabic specialization value to key + * Example: "Cardiology" or "أمراض القلب" -> "CARDIOLOGY" + */ +export function TransformSpecialization() { + return Transform(({ value }) => { + if (!value || typeof value !== 'string') { + return value; + } + + // If it's already a key (uppercase with underscores), return as is + if (value === value.toUpperCase() && /^[A-Z_]+$/.test(value)) { + return value; + } + + // Try to get the key from the display value (EN or AR) + const key = getSpecializationKey(value); + return key || value; // Return key if found, otherwise return original value for validation to catch + }); +} + +/** + * Format specialization response based on language preference + * @param key - The specialization key stored in DB + * @param lang - Language preference ('en' or 'ar') + * @returns Formatted specialization object + */ +export function formatSpecializationResponse(key: SpecializationKey, lang?: 'en' | 'ar') { + const { getSpecialization } = require('@/constants/specializations'); + const spec = getSpecialization(key); + + if (!spec) { + return { key, value: key }; + } + + // If language is specified, return only that language + if (lang === 'en') { + return { key, value: spec.en }; + } else if (lang === 'ar') { + return { key, value: spec.ar }; + } + + // Default: return both languages + return { + key, + en: spec.en, + ar: spec.ar, + }; +} diff --git a/src/validators/specialization.validator.ts b/src/validators/specialization.validator.ts new file mode 100644 index 0000000..00d9272 --- /dev/null +++ b/src/validators/specialization.validator.ts @@ -0,0 +1,40 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; +import { isValidSpecialization, VALID_SPECIALIZATIONS_EN, VALID_SPECIALIZATIONS_AR, SpecializationEnglishValue, SpecializationArabicValue } from '@/constants/specializations'; + +export function IsValidSpecialization(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isValidSpecialization', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + if (typeof value !== 'string') { + return false; + } + + // Check if it's a valid key + if (isValidSpecialization(value)) { + return true; + } + + // Check if it's a valid English value + if (VALID_SPECIALIZATIONS_EN.includes(value as SpecializationEnglishValue)) { + return true; + } + + // Check if it's a valid Arabic value + if (VALID_SPECIALIZATIONS_AR.includes(value as SpecializationArabicValue)) { + return true; + } + + return false; + }, + defaultMessage(args: ValidationArguments) { + return `${args.property} must be a valid specialization (you can use English name, Arabic name, or key)`; + }, + }, + }); + }; +} diff --git a/tsconfig.json b/tsconfig.json index 18885ab..700d863 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,9 @@ "@middlewares/*": ["middlewares/*"], "@routes/*": ["routes/*"], "@services/*": ["services/*"], - "@utils/*": ["utils/*"] + "@utils/*": ["utils/*"], + "@constants/*": ["constants/*"], + "@validators/*": ["validators/*"] } }, "include": ["src/**/*.ts", "src/**/*.json", ".env"], From 98a3c622d67f7595bad1c3179e5b9d07625bb8b8 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 12 Dec 2025 16:53:01 +0200 Subject: [PATCH 044/210] refactor services / CatchAsync --- src/controllers/medical-records.controller.ts | 95 +++++++++---------- src/middlewares/upload.middleware.ts | 4 +- src/services/ipfs.service.ts | 69 ++++++++------ src/services/medical-records.service.ts | 94 +++++++++--------- src/utils/catchAsync.ts | 15 +++ 5 files changed, 151 insertions(+), 126 deletions(-) create mode 100644 src/utils/catchAsync.ts diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index da72423..0023958 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -1,79 +1,74 @@ import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { Request, Response, NextFunction } from 'express'; -import * as MedicalRecordService from '@/services/medical-records.service' -import { RequestWithUser } from '@/interfaces/auth.interface'; +import { RequestWithUser } from '@/interfaces/auth.interface'; import { promises } from 'dns'; +import { MedicalRecordService } from '@/services/medical-records.service'; +import { catchAsync } from '@/utils/catchAsync'; -// upload a new medical record -export const uploadRecord = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try{ - if (!req.file){ - res.status(400).json({message: 'No file uploaded'}); +export class MedicalRecordController { + + constructor(private medicalRecordService: MedicalRecordService) { } + + // upload a new medical record + public uploadRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + if (!req.file) { + res.status(400).json({ message: 'No file uploaded' }); } - const record_data: CreateMedicalRecordDto = req.body; - const patient_id = req.user.id; - const file_buffer = req.file.buffer; - const file_name = req.file.originalname; + const recordData: CreateMedicalRecordDto = req.body; + const patientId = req.user.id; + const fileBuffer = req.file.buffer; + const fileName = req.file.originalname; - const medical_record = await MedicalRecordService.createMedicalRecord(patient_id, record_data, file_buffer, file_name); + const medical_record = await this.medicalRecordService.createMedicalRecord(patientId, recordData, fileBuffer, fileName); res.status(201).json({ message: 'uploaded MR successfully', data: medical_record, }); - } - catch(e){ - next(e); - } -}; - - -// get all MRs for a patient + }); -export const getPatientMedicalRecords = async (req: RequestWithUser, res: Response, next: NextFunction): Promise =>{ - try{ - const patient_id = req.user.id; + // get all MRs for a patient + public getPatientMedicalRecords = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; - const records = await MedicalRecordService.getPatientRecords(patient_id); + const records = await this.medicalRecordService.getPatientRecords(patientId); res.status(201).json({ message: 'MRs retrieved successfully', data: records, }); - } - catch(e){ - next(e); - } -}; - + }); -// get all MRs for a doctor -export const getDocrotMedicalRecords = async (req: RequestWithUser, res: Response, next:NextFunction): Promise => { - try{ - const doctor_id = req.user.id; + // get all MRs for a doctor + public getDocrotMedicalRecords = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; - const records = await MedicalRecordService.getDoctorRecords(doctor_id); + const records = await this.medicalRecordService.getDoctorRecords(doctorId); res.status(201).json({ message: 'MRs retrieved successfully', data: records, }); - } - catch(e){ - next(e); - } -}; + }); -export const deleteRecord = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const record_id = req.params.id; - await MedicalRecordService.deleteRecord(record_id); + public deleteRecord = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { + const record_id = req.params.id; - res.status(200).json({ - message: 'deleted MR successfully', + await this.medicalRecordService.deleteRecord(record_id); + + res.status(200).json({ + message: 'deleted MR successfully', + }); }); - } catch (e) { - next(e); - } -}; \ No newline at end of file + +} + + + + + + + + + diff --git a/src/middlewares/upload.middleware.ts b/src/middlewares/upload.middleware.ts index 2a7fd36..bf5c202 100644 --- a/src/middlewares/upload.middleware.ts +++ b/src/middlewares/upload.middleware.ts @@ -4,7 +4,7 @@ import { HttpException } from "@/exceptions/HttpException"; const storage = multer.memoryStorage(); -const allowed_file_types = [ +const AllowedFileTypes = [ 'application/pdf', 'image/jpeg', 'image/jpg', @@ -17,7 +17,7 @@ const allowed_file_types = [ const fileFilter = (req: Request, file: Express.Multer.File, cb: FileFilterCallback) => { - if (allowed_file_types.includes(file.mimetype)) { + if (AllowedFileTypes.includes(file.mimetype)) { cb(null, true); } else { diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index 2dac57b..bd3750b 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -1,49 +1,64 @@ import { promises } from 'dns'; import { port } from 'envalid' -import {create, IPFSHTTPClient} from 'ipfs-http-client' +import { create, IPFSHTTPClient } from 'ipfs-http-client' import { HttpException } from '@/exceptions/HttpException'; +import { Service } from 'typedi'; -// temp --> selecting the pinning service (4EVERLAND) -const ipfs_client: IPFSHTTPClient = create({ - host: process.env.IPFS_HOST, - port: parseInt(process.env.IPFS_PORT), - protocol: process.env.IPFS_PROTOCOL -}); +@Service() +export class IpfsService { + private ipfsClient: IPFSHTTPClient; -// upload med file to IPFS --> generate and return CID -export const uploadFile = async(fileData: Buffer, fileName: string): Promise => { - try{ - const result = await ipfs_client.add({ + constructor() { + // temp --> selecting the pinning service (4EVERLAND) + this.ipfsClient = create({ + host: process.env.IPFS_HOST, + port: parseInt(process.env.IPFS_PORT), + protocol: process.env.IPFS_PROTOCOL + }); + } + + // upload med file to IPFS --> generate and return CID + public async uploadFile(fileData: Buffer, fileName: string): Promise { + const result = await this.ipfsClient.add({ path: fileName, content: fileData, }); const cid = result.cid.toString(); return cid - } - catch(e){ - console.error('failed to upload to IPFS', e); - throw new HttpException(500, 'failed to upload to IPFS'); - } -}; - + }; -// get file using CID -export const getFile = async (cid: string): Promise => { - try{ + // get file using CID + public async getFile(cid: string): Promise { // note --> each chunk in ipfs is Uint8Array const chunks: Uint8Array[] = []; - for await (const chunk of ipfs_client.cat(cid)){ + for await (const chunk of this.ipfsClient.cat(cid)) { chunks.push(chunk); } const fileData = Buffer.concat(chunks); return fileData; } - catch(e){ - console.error('failed to retrieve from IPFS:', e) - throw new HttpException(404, 'file not found') - } + + // pin management --> TBD + + } -// pin management --> TBD \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 0e1a25d..8dc9433 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -1,20 +1,25 @@ import { PrismaClient } from '@prisma/client'; import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; -import { uploadFile, getFile } from '@/services/ipfs.service'; import { MedicalRecord } from '@/interfaces/medicalRecords.interface'; import prisma from '@/config/prisma'; +import { Service } from 'typedi'; +import { IpfsService } from '@/services/ipfs.service'; -// create a new MR -export const createMedicalRecord = async ( - patient_id: string, - fileData: CreateMedicalRecordDto, - fileBuffer: Buffer, - fileName: string, -): Promise => { - try { +@Service() +export class MedicalRecordService { + + constructor(private ipfsService: IpfsService) { } + + // create a new MR + public async createMedicalRecord( + patientId: string, + fileData: CreateMedicalRecordDto, + fileBuffer: Buffer, + fileName: string, + ): Promise { // upload to IPFS and get cid - const cid = await uploadFile(fileBuffer, fileName); + const cid = await this.ipfsService.uploadFile(fileBuffer, fileName); console.log(`file is uploaded to ipfs, cid:" ${cid}`) // blockchain stuff @@ -22,7 +27,7 @@ export const createMedicalRecord = async ( // save to db const medicalRecord = await prisma.medicalRecord.create({ data: { - patient_id: patient_id, + patient_id: patientId, doctor_id: fileData.doctor_id || null, name: fileData.name, cid: cid, @@ -34,22 +39,14 @@ export const createMedicalRecord = async ( }); return medicalRecord; - - } - catch (e) { - console.error('error creating medical record:', e); - throw new HttpException(500, 'failed to create medical record'); } -} - -// get all medical records for a specific patient + // get all medical records for a specific patient -export const getPatientRecords = async (patient_id: string): Promise => { - try { + public async getPatientRecords(patientId: string): Promise { const records = await prisma.medicalRecord.findMany({ where: { - patient_id: patient_id, + patient_id: patientId, deleted_at: null, }, orderBy: { @@ -62,18 +59,12 @@ export const getPatientRecords = async (patient_id: string): Promise => { - try { + // get MR shared with a doctor + public async getDoctorRecords(doctorId: string): Promise { const records = await prisma.medicalRecord.findMany({ where: { - doctor_id: doctor_id, + doctor_id: doctorId, deleted_at: null, }, orderBy: { @@ -86,19 +77,13 @@ export const getDoctorRecords = async (doctor_id: string): Promise { - try { + // delete any MR (soft) + public async deleteRecord(recordId: string) { // checking if it's already deleted const record = await prisma.medicalRecord.findFirst({ where: { - id: record_id, + id: recordId, deleted_at: null, }, }); @@ -108,17 +93,32 @@ export const deleteRecord = async (record_id: string) => { await prisma.medicalRecord.update({ where: { - id: record_id, + id: recordId, }, data: { deleted_at: new Date(), }, }); } - catch (e) { - console.error('Error deleting medical record:', e); - throw new HttpException(500, 'failed to delete medical record'); - } + // get a specific MR by id?? + // handle permissions --> fabric stuff + } -// get a specific MR by id?? -// handle permissions --> fabric stuff \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/src/utils/catchAsync.ts b/src/utils/catchAsync.ts new file mode 100644 index 0000000..4dbd3c3 --- /dev/null +++ b/src/utils/catchAsync.ts @@ -0,0 +1,15 @@ +import { Request, Response, NextFunction } from 'express'; + +type AsyncFunction = ( + req: Request, + res: Response, + next: NextFunction +) => Promise; + + +export const catchAsync = (fn: AsyncFunction) => { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +}; + From 0ef6ed44d6839050cf905d77c3ea737039eb4939 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 12 Dec 2025 18:15:42 +0200 Subject: [PATCH 045/210] feat: add super admin role and implement admin management features; update routes, services, and Swagger documentation --- src/controllers/admin.controller.ts | 24 +- src/controllers/superAdmin.controller.ts | 17 + src/dtos/admins.dto.ts | 19 +- src/dtos/superAdmins.dto.ts | 15 +- .../migration.sql | 2 + src/prisma/schema.prisma | 1 + src/routes/admin.route.ts | 201 +++++++++- src/routes/superAdmin.route.ts | 379 +++++++++++++++++- src/services/admin.service.ts | 73 +++- src/services/superAdmin.service.ts | 56 ++- src/swagger.js | 2 +- swagger-output.json | 0 12 files changed, 755 insertions(+), 34 deletions(-) create mode 100644 src/prisma/migrations/20251212135524_added_super_admin_role/migration.sql delete mode 100644 swagger-output.json diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 4c4db57..cb4be03 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -11,17 +11,21 @@ export class AdminController { public adminService = Container.get(AdminService); public addDoctor = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { - try { - const doctorData: AddDoctorFromAdminDto = req.body; - const newDoctor = await this.adminService.addDoctor(doctorData); + const doctorData: AddDoctorFromAdminDto = req.body; + const newDoctor = await this.adminService.addDoctor(doctorData); - res.status(201).json({ - data: newDoctor, - message: 'Doctor added successfully' - }); - } catch (error) { - next(error); - } + const formattedNewDoctor = newDoctor.doctor ? { + ...newDoctor.doctor, + specialization: formatSpecializationResponse( + newDoctor.doctor.specialization as SpecializationKey, + req.language + ), + } : null; + + res.status(201).json({ + data: formattedNewDoctor, + message: 'Doctor added successfully' + }); }; public getAllDoctors = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { diff --git a/src/controllers/superAdmin.controller.ts b/src/controllers/superAdmin.controller.ts index 94edbfe..53922da 100644 --- a/src/controllers/superAdmin.controller.ts +++ b/src/controllers/superAdmin.controller.ts @@ -15,4 +15,21 @@ export class SuperAdminController { message: 'Admin added successfully' }); } + + public getAllAdmins = async (req: Request, res: Response, next: NextFunction): Promise => { + const admins = await this.superAdminService.getAllAdmins(); + res.status(200).json({ + data: admins, + message: 'Admins retrieved successfully' + }); + } + + public getAdminById = async (req: Request, res: Response, next: NextFunction): Promise => { + const adminId: string = req.params.id; + const admin = await this.superAdminService.getAdminById(adminId); + res.status(200).json({ + data: admin, + message: 'Admin retrieved successfully' + }); + } } \ No newline at end of file diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 2f9aa41..00e9297 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -1,4 +1,4 @@ -import { Gender } from "@prisma/client"; +import { Gender, Role } from "@prisma/client"; import { IsEmail, IsNotEmpty, IsString } from "class-validator"; import { IsValidSpecialization } from "@/validators/specialization.validator"; import { TransformSpecialization } from "@/utils/specializationTransform"; @@ -26,4 +26,21 @@ export class AddDoctorFromAdminDto { message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' }) public specialization: string; +} + +export class DoctorFromAdminResponseDto { + public name: string; + public email: string; + public username: string; + public phone: string; + public gender: Gender; + public date_of_birth: Date; + public role: Role; + public isVerified: boolean; + public hasCompletedProfile: boolean + public photoUrl?: string; + public doctor?: { + specialization: string; + avg_time?: Date; + }; } \ No newline at end of file diff --git a/src/dtos/superAdmins.dto.ts b/src/dtos/superAdmins.dto.ts index 74a722b..ae9d965 100644 --- a/src/dtos/superAdmins.dto.ts +++ b/src/dtos/superAdmins.dto.ts @@ -1,4 +1,4 @@ -import { Gender } from "@prisma/client"; +import { Gender, Role } from "@prisma/client"; import { IsEmail, IsNotEmpty, IsString } from "class-validator"; export class AddAdminFromSuperAdminDto { @@ -25,4 +25,17 @@ export class AddAdminFromSuperAdminDto { @IsString() @IsNotEmpty() public date_of_birth: string; +} + +export class AdminFromSuperAdminResponseDto { + public email: string; + public name: string; + public username: string; + public phone: string; + public role: Role; + public gender: Gender; + public isVerified: boolean; + public hasCompletedProfile: boolean; + public date_of_birth: Date; + public photo_url?: string; } \ No newline at end of file diff --git a/src/prisma/migrations/20251212135524_added_super_admin_role/migration.sql b/src/prisma/migrations/20251212135524_added_super_admin_role/migration.sql new file mode 100644 index 0000000..9704e58 --- /dev/null +++ b/src/prisma/migrations/20251212135524_added_super_admin_role/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "Role" ADD VALUE 'SUPER_ADMIN'; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index f01aa0f..d493357 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -252,6 +252,7 @@ enum Gender { } enum Role { + SUPER_ADMIN ADMIN DOCTOR NURSE diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 9aef22e..8a20640 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -19,7 +19,74 @@ export class AdminRoute implements Routes { private initializeRoutes() { this.router.post( `${this.path}/doctors`, - /* #swagger.tags = ['Admin'] */ + /* #swagger.tags = ['Admin'] + #swagger.summary = 'Add a new doctor' + #swagger.description = 'Admin endpoint to add a new doctor to the system' + #swagger.security = [{ "bearerAuth": []} ,{ cookieAuth: []}] + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["email", "name", "phone", "gender","specialization"], + properties: { + email: { type: "string", format: "email", example: "doctor@example.com" }, + name: { type: "string", example: "John" }, + phone: { type: "string", example: "+1234567890" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + specialization: { type: "string", example: "CARDIOLOGY" } + } + } + } + } + } + #swagger.responses[201] = { + description: "Doctor added successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + email: { type: "string" }, + name: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + role: { type: "string" }, + date_of_birth: { type: "string", format: "date-time"}, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photo_url: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + }, + message: { type: "string", example: "Doctor added successfully" } + } + } + } + } + } + #swagger.responses[400] = { description: "Bad request - Invalid input data" } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ AuthMiddleware, RoleMiddleware(Role.ADMIN), LanguageMiddleware, @@ -29,7 +96,65 @@ export class AdminRoute implements Routes { this.router.get( `${this.path}/doctors`, - /* #swagger.tags = ['Admin'] */ + /* #swagger.tags = ['Admin'] + #swagger.summary = 'Get all doctors' + #swagger.description = 'Retrieve a list of all doctors in the system' + #swagger.security = [{ "bearerAuth": [] } , { cookieAuth: [] }] + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string', + example: 'en' + } + #swagger.responses[200] = { + description: "Doctors retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + date_of_birth: { type: "string", format: "date-time" }, + role: { type: "string" }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photo_url: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + } + }, + message: { type: "string", example: "Doctors retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ AuthMiddleware, RoleMiddleware(Role.ADMIN), LanguageMiddleware, @@ -38,11 +163,81 @@ export class AdminRoute implements Routes { this.router.get( `${this.path}/doctors/:id`, - /* #swagger.tags = ['Admin'] */ + /* #swagger.tags = ['Admin'] + #swagger.summary = 'Get doctor by ID' + #swagger.description = 'Retrieve a specific doctor\'s details by their ID' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.parameters['id'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string', + example: 'en' + } + #swagger.responses[200] = { + description: "Doctor retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + date_of_birth: { type: "string", format: "date-time" }, + role: { type: "string" }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photo_url: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + }, + message: { type: "string", example: "Doctor retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + #swagger.responses[404] = { description: "Doctor not found" } + */ AuthMiddleware, RoleMiddleware(Role.ADMIN), LanguageMiddleware, this.adminController.getDoctorById, ); + + this.router.patch( + `${this.path}/doctors/:id`, + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.updateDoctorVerificationStatus, + ); } } \ No newline at end of file diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 2973c1c..a3a1500 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -1,7 +1,12 @@ +import { AdminController } from "@/controllers/admin.controller"; import { SuperAdminController } from "@/controllers/superAdmin.controller"; +import { AddDoctorFromAdminDto } from "@/dtos/admins.dto"; import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; import { Routes } from "@/interfaces"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; +import { LanguageMiddleware } from "@/middlewares/language.middleware"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Role } from "@prisma/client"; import { Router } from "express"; @@ -9,19 +14,385 @@ export class SuperAdminRoute implements Routes { public path = '/super-admin'; public router = Router(); public superAdminController = new SuperAdminController(); + public adminController = new AdminController(); constructor() { this.initializeRoutes(); } private initializeRoutes() { + + // ADMIN ROUTES this.router.post( `${this.path}/admins`, - /* #swagger.tags = ['Super Admin'] */ - // AuthMiddleware, - // RoleMiddleware(Role.SUPER_ADMIN), - // LanguageMiddleware, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Add a new admin' + #swagger.description = 'Super admin endpoint to add a new admin to the system' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["email", "name", "password", "phone", "gender", "date_of_birth"], + properties: { + email: { type: "string", format: "email", example: "admin@example.com" }, + name: { type: "string", example: "Jane Smith" }, + password: { type: "string", format: "password", example: "SecurePass123!" }, + phone: { type: "string", example: "+1234567890" }, + gender: { type: "string", enum: ["MALE", "FEMALE"], example: "FEMALE" }, + date_of_birth: { type: "string", format: "date", example: "1990-01-01" } + } + } + } + } + } + #swagger.responses[201] = { + description: "Admin added successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + email: { type: "string" }, + name: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + role: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + date_of_birth: { type: "string", format: "date-time" }, + photo_url: { type: "string", nullable: true } + } + }, + message: { type: "string", example: "Admin added successfully" } + } + } + } + } + } + #swagger.responses[400] = { description: "Bad request - Invalid input data" } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), ValidationMiddleware(AddAdminFromSuperAdminDto), this.superAdminController.addAdmin, ); + + this.router.get( + `${this.path}/admins`, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Get all admins' + #swagger.description = 'Retrieve a list of all admins in the system' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.responses[200] = { + description: "Admins retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "array", + items: { + type: "object", + properties: { + email: { type: "string" }, + name: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + role: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + date_of_birth: { type: "string", format: "date-time" }, + photo_url: { type: "string", nullable: true } + } + } + }, + message: { type: "string", example: "Admins retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.superAdminController.getAllAdmins, + ); + + this.router.get( + `${this.path}/admins/:id`, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Get admin by ID' + #swagger.description = 'Retrieve a specific admin\'s details by their ID' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.parameters['id'] = { + in: 'path', + description: 'Admin ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: "Admin retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + email: { type: "string" }, + name: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + role: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + date_of_birth: { type: "string", format: "date-time" }, + photo_url: { type: "string", nullable: true } + } + }, + message: { type: "string", example: "Admin retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + #swagger.responses[404] = { description: "Admin not found" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.superAdminController.getAdminById, + ) + + // DOCTOR ROUTES + this.router.post( + `${this.path}/doctors`, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Add a new doctor' + #swagger.description = 'Super admin endpoint to add a new doctor to the system' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["email", "name", "phone", "gender", "specialization"], + properties: { + email: { type: "string", format: "email", example: "doctor@example.com" }, + name: { type: "string", example: "John Doe" }, + phone: { type: "string", example: "+1234567890" }, + gender: { type: "string", enum: ["MALE", "FEMALE"], example: "MALE" }, + specialization: { type: "string", example: "CARDIOLOGY" } + } + } + } + } + } + #swagger.responses[201] = { + description: "Doctor added successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + email: { type: "string" }, + name: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string" }, + role: { type: "string" }, + date_of_birth: { type: "string", format: "date-time" }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photoUrl: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + }, + message: { type: "string", example: "Doctor added successfully" } + } + } + } + } + } + #swagger.responses[400] = { description: "Bad request - Invalid input data" } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + LanguageMiddleware, + ValidationMiddleware(AddDoctorFromAdminDto), + this.adminController.addDoctor, + ); + + this.router.get( + `${this.path}/doctors`, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Get all doctors' + #swagger.description = 'Retrieve a list of all doctors in the system' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string', + example: 'en' + } + #swagger.responses[200] = { + description: "Doctors retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + date_of_birth: { type: "string", format: "date-time" }, + role: { type: "string" }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photo_url: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + } + }, + message: { type: "string", example: "Doctors retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + LanguageMiddleware, + this.adminController.getAllDoctors, + ); + + this.router.get( + `${this.path}/doctors/:id`, + /* #swagger.tags = ['Super Admin'] + #swagger.summary = 'Get doctor by ID' + #swagger.description = 'Retrieve a specific doctor\'s details by their ID' + #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] + #swagger.parameters['id'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string', + example: 'en' + } + #swagger.responses[200] = { + description: "Doctor retrieved successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" }, + username: { type: "string" }, + phone: { type: "string" }, + gender: { type: "string", enum: ["MALE", "FEMALE"] }, + date_of_birth: { type: "string", format: "date-time" }, + role: { type: "string" }, + isVerified: { type: "boolean" }, + hasCompletedProfile: { type: "boolean" }, + photo_url: { type: "string", nullable: true }, + doctor: { + type: "object", + nullable: true, + properties: { + specialization: { + type: "object", + properties: { + key: { type: "string" }, + value: { type: "string" } + } + }, + avg_time: { type: "number", nullable: true } + } + } + } + }, + message: { type: "string", example: "Doctor retrieved successfully" } + } + } + } + } + } + #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } + #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + #swagger.responses[404] = { description: "Doctor not found" } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + LanguageMiddleware, + this.adminController.getDoctorById, + ) } } \ No newline at end of file diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 30ac7d4..da43d81 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -1,7 +1,7 @@ import { PrismaClient, Role } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; -import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto } from '@/dtos/admins.dto'; import { HttpException } from '@/exceptions/HttpException'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import { User } from '@/interfaces'; @@ -11,7 +11,7 @@ const prisma = new PrismaClient(); @Service() export class AdminService { - public async addDoctor(doctorData: AddDoctorFromAdminDto): Promise { + public async addDoctor(doctorData: AddDoctorFromAdminDto): Promise { // Check if email already exists const existingUser = await prisma.user.findUnique({ where: { email: doctorData.email } @@ -52,6 +52,24 @@ export class AdminService { role: Role.DOCTOR, isVerified: true, hasCompletedProfile: false, + }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + doctor: { + select: { + specialization: true, + } + }, } }); @@ -62,31 +80,62 @@ export class AdminService { specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") } }); - - return createdUser; + const { id, ...createdDoctor } = createdUser; + return createdDoctor; } - public async getAllDoctors() { + public async getAllDoctors(): Promise { const doctors = await prisma.user.findMany({ where: { role: Role.DOCTOR }, - include: { - doctor: true, - }, + select: { + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + doctor: { + select: { + specialization: true, + avg_time: true + } + }, + } }); return doctors; } - public async getDoctorById(id: string) { + public async getDoctorById(id: string): Promise { const doctor = await prisma.user.findUnique({ where: { id, role: Role.DOCTOR }, - include: { - doctor: true, - }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + doctor: { + select: { + specialization: true, + avg_time: true + } + }, + } }); if (!doctor) { diff --git a/src/services/superAdmin.service.ts b/src/services/superAdmin.service.ts index 059c7a9..43c176e 100644 --- a/src/services/superAdmin.service.ts +++ b/src/services/superAdmin.service.ts @@ -1,4 +1,5 @@ -import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; +import { AddAdminFromSuperAdminDto, AdminFromSuperAdminResponseDto } from "@/dtos/superAdmins.dto"; +import { User } from "@/interfaces"; import { PrismaClient, Role } from "@prisma/client"; import { hash } from "bcrypt"; import { Service } from "typedi"; @@ -8,7 +9,7 @@ const prisma = new PrismaClient(); @Service() export class SuperAdminService { - public async addAdmin(adminData: AddAdminFromSuperAdminDto): Promise { + public async addAdmin(adminData: AddAdminFromSuperAdminDto): Promise { // Logic to add a new admin // Check if email already exists const existingUser = await prisma.user.findUnique({ @@ -41,8 +42,59 @@ export class SuperAdminService { isVerified: true, hasCompletedProfile: true, date_of_birth: new Date(adminData.date_of_birth), + }, + select: { + email: true, + name: true, + username: true, + phone: true, + role: true, + gender: true, + isVerified: true, + hasCompletedProfile: true, + date_of_birth: true, + photo_url: true, } }); return newAdmin; } + + public async getAllAdmins(): Promise { + const admins = await prisma.user.findMany({ + where: { role: Role.ADMIN }, + select: { + email: true, + name: true, + username: true, + phone: true, + role: true, + gender: true, + isVerified: true, + hasCompletedProfile: true, + date_of_birth: true, + photo_url: true, + } + }); + return admins; + } + + public async getAdminById(adminId: string): Promise { + const admin = await prisma.user.findUnique({ + where: { id: adminId, role: Role.ADMIN }, + select: { + email: true, + name: true, + username: true, + phone: true, + role: true, + gender: true, + isVerified: true, + hasCompletedProfile: true, + date_of_birth: true, + photo_url: true, + } + }); + return admin; + } + } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index 6ea1337..dd36c01 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,6 +14,6 @@ const doc = { }; const outputFile = './swagger-output.json'; -const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts']; +const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/admin.route.ts', './src/routes/superAdmin.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/swagger-output.json b/swagger-output.json deleted file mode 100644 index e69de29..0000000 From 0dcfd3595c6f97941131b82f9912c89d9600374d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 12 Dec 2025 19:45:48 +0200 Subject: [PATCH 046/210] refactor controllers/services --- src/controllers/auth.controller.ts | 207 ++++++++++------------- src/controllers/googleAuth.controller.ts | 29 ++-- src/services/auth.service.ts | 69 ++++---- src/services/googleAuth.service.ts | 98 +++++------ src/services/ipfs.service.ts | 20 +-- src/services/medical-records.service.ts | 20 +-- 6 files changed, 171 insertions(+), 272 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 87e6bd0..5df671e 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -4,132 +4,97 @@ import { RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { AuthService } from '@services/auth.service'; import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@/dtos/users.dto'; +import { catchAsync } from '@/utils/catchAsync'; export class AuthController { public auth = Container.get(AuthService); - public signUp = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userData: CreateUserDto = req.body; - const { createdUserData, cookies } = await this.auth.signup(userData); - - res.setHeader('Set-Cookie', cookies); - - await this.auth.sendEmailOtp(userData.email); - - res.status(201).json({ data: createdUserData, message: 'Signed Up Successfully' }); - } catch (error) { - next(error); - } - }; - - public logIn = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const userData: LoginUserDto = req.body; - const { cookies, findUser } = await this.auth.login(userData); - - res.setHeader('Set-Cookie', cookies); - res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); - } catch (error) { - next(error); - } - }; - - public logOut = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const userData: User = req.user; - const logOutUserData: User = await this.auth.logout(userData); - - res.setHeader('Set-Cookie', [ - 'Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax', - 'RefreshToken=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax' - ]); - - res.status(200).json({ message: 'Logged Out Successfully' }); - } catch (error) { - next(error); - } - }; - - public refresh = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const refreshToken = req.cookies?.RefreshToken; - const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); - - res.setHeader('Set-Cookie', cookies); - res.status(200).json({ - data: { - user, - accessToken: { - expiresIn: accessToken.expiresIn, - expiresAt: new Date(Date.now() + accessToken.expiresIn * 1000) - } - }, - message: 'Token Refreshed Successfully' - }); - } catch (error) { - next(error); - } - }; - - public completeProfile = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const userData: User = req.user; - const profileData: CompleteUserProfileDto = req.body; - const updatedUserData: User = await this.auth.completeProfile(userData, profileData); - - res.status(200).json({ data: updatedUserData, message: 'Profile Completed Successfully' }); - } catch (error) { - next(error); - } - }; - - public verifyOTP = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const email = await this.auth.getUserEmail(req) - const { otp } = req.body; - if (!otp) { - throw new Error('OTP is required'); - } - const isSuccessful = await this.auth.verifyEmailOtp(email, otp); - res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); - } catch (error) { - next(error); - } - }; - - public forgetPassword = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const email = req.body.email; - if (!email) { - throw new Error('Email is required'); - } - await this.auth.sendPasswordResetEmail(email); - res.status(200).json({ message: 'Password Reset Email Sent Successfully' }); - } catch (error) { - next(error); - } - }; - - public resetPassword = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const { token, newPassword }: ResetPasswordDto = req.body; - - await this.auth.resetPassword(token, newPassword); - res.status(200).json({ message: 'Password Reset Successfully' }); - } catch (error) { - next(error); + public signUp = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { + const userData: CreateUserDto = req.body; + const { createdUserData, cookies } = await this.auth.signup(userData); + + res.setHeader('Set-Cookie', cookies); + + await this.auth.sendEmailOtp(userData.email); + + res.status(201).json({ data: createdUserData, message: 'Signed Up Successfully' }); + }); + + public logIn = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { + const userData: LoginUserDto = req.body; + const { cookies, findUser } = await this.auth.login(userData); + + res.setHeader('Set-Cookie', cookies); + res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); + }); + + public logOut = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const userData: User = req.user; + const logOutUserData: User = await this.auth.logout(userData); + + res.setHeader('Set-Cookie', [ + 'Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax', + 'RefreshToken=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax' + ]); + + res.status(200).json({ message: 'Logged Out Successfully' }); + }); + + public refresh = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { + const refreshToken = req.cookies?.RefreshToken; + const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); + + res.setHeader('Set-Cookie', cookies); + res.status(200).json({ + data: { + user, + accessToken: { + expiresIn: accessToken.expiresIn, + expiresAt: new Date(Date.now() + accessToken.expiresIn * 1000) + } + }, + message: 'Token Refreshed Successfully' + }); + }); + + public completeProfile = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const userData: User = req.user; + const profileData: CompleteUserProfileDto = req.body; + const updatedUserData: User = await this.auth.completeProfile(userData, profileData); + + res.status(200).json({ data: updatedUserData, message: 'Profile Completed Successfully' }); + }); + + public verifyOTP = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const email = await this.auth.getUserEmail(req) + const { otp } = req.body; + if (!otp) { + throw new Error('OTP is required'); } - }; - - public resendOTP = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const email = await this.auth.getUserEmail(req) - await this.auth.sendEmailOtp(email); - res.status(200).json({ message: 'OTP Resent Successfully' }); - } catch (error) { - next(error); + const isSuccessful = await this.auth.verifyEmailOtp(email, otp); + res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); + }); + + public forgetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const email = req.body.email; + if (!email) { + throw new Error('Email is required'); } - } + await this.auth.sendPasswordResetEmail(email); + res.status(200).json({ message: 'Password Reset Email Sent Successfully' }); + }); + + public resetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const { token, newPassword }: ResetPasswordDto = req.body; + + await this.auth.resetPassword(token, newPassword); + res.status(200).json({ message: 'Password Reset Successfully' }); + }); + + public resendOTP = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const email = await this.auth.getUserEmail(req) + await this.auth.sendEmailOtp(email); + res.status(200).json({ message: 'OTP Resent Successfully' }); + }); } diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index fb37847..c184786 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -5,6 +5,7 @@ import { NextFunction, Request, Response } from "express"; import { User, UserLoginData } from "@/interfaces/users.interface"; import { RequestWithUser } from "@/interfaces"; import { GoogleAuthService } from "@/services/googleAuth.service"; +import { catchAsync } from "@/utils/catchAsync"; export class GoogleAuthController { public authService = Container.get(AuthService); @@ -35,7 +36,7 @@ export class GoogleAuthController { // Check if it's a new user from the info object const isNewUser = info?.isNewUser || false; - + // Redirect based on whether it's first time or not if (isNewUser) { res.redirect(`${process.env.FRONTEND_URL}/api/auth/google-callback`); @@ -48,22 +49,14 @@ export class GoogleAuthController { })(req, res, next); }; - public updatePhoneNumber = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const phone: string = req.body.phone; - await this.googleAuthService.updatePhoneNumber(req.user.id, phone); - res.status(200).json({ message: 'Phone Number Updated Successfully' }); - } catch (error) { - next(error); - } - }; + public updatePhoneNumber = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const phone: string = req.body.phone; + await this.googleAuthService.updatePhoneNumber(req.user.id, phone); + res.status(200).json({ message: 'Phone Number Updated Successfully' }); + }); - public getGoogleUserData = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - try { - const googleUserData: UserLoginData = await this.googleAuthService.getGoogleUserData(req.user.id); - res.status(200).json({ data: googleUserData, message: 'Google User Data Retrieved Successfully' }); - } catch (error) { - next(error); - } - }; + public getGoogleUserData = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const googleUserData: UserLoginData = await this.googleAuthService.getGoogleUserData(req.user.id); + res.status(200).json({ data: googleUserData, message: 'Google User Data Retrieved Successfully' }); + }); } \ No newline at end of file diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 63d205d..ccc161e 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -88,7 +88,7 @@ export class AuthService { isVerified, hasCompletedProfile }; - + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); @@ -191,45 +191,40 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - try { - // Verify the refresh token - const secretKey: string = REFRESH_TOKEN_SECRET; - const decoded = verify(refreshToken, secretKey) as DataStoredInToken; - - // Hash the token to compare with stored hash - const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); - - // Check if refresh token exists and is not revoked - const storedToken = await this.refreshTokens.findFirst({ - where: { - token_hash: tokenHash, - user_id: decoded.id, - is_revoked: false, - expires_at: { gt: new Date() }, - }, - }); - - if (!storedToken) { - const error = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); - throw new HttpException(error.status, error.message, error.messageAr); - } + // Verify the refresh token + const secretKey: string = REFRESH_TOKEN_SECRET; + const decoded = verify(refreshToken, secretKey) as DataStoredInToken; - // Get user - const user = await this.users.findUnique({ where: { id: decoded.id } }); - if (!user) { - const error = createBilingualError(401, ErrorMessages.USER_NOT_EXIST); - throw new HttpException(error.status, error.message, error.messageAr); - } + // Hash the token to compare with stored hash + const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); - // Create new access token - const accessToken = this.createAccessToken(user); - const cookies = this.createCookies({ accessToken }); + // Check if refresh token exists and is not revoked + const storedToken = await this.refreshTokens.findFirst({ + where: { + token_hash: tokenHash, + user_id: decoded.id, + is_revoked: false, + expires_at: { gt: new Date() }, + }, + }); - return { cookies, user, accessToken }; - } catch (error) { - const err = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); - throw new HttpException(err.status, err.message, err.messageAr); + if (!storedToken) { + const error = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); + throw new HttpException(error.status, error.message, error.messageAr); } + + // Get user + const user = await this.users.findUnique({ where: { id: decoded.id } }); + if (!user) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_EXIST); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // Create new access token + const accessToken = this.createAccessToken(user); + const cookies = this.createCookies({ accessToken }); + + return { cookies, user, accessToken }; } // public async revokeRefreshToken(refreshToken: string): Promise { @@ -288,7 +283,7 @@ export class AuthService { await transporter.sendMail(mailOptions); } - + public async getUserEmail(req: RequestWithUser): Promise { const email = await this.users.findUnique({ where: { id: req.user.id }, diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index b13983d..2b03e3b 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -11,72 +11,54 @@ import prisma from "@/config/prisma"; export class GoogleAuthService { public async createInitialProfileGoogle(newUserData: CreateGoogleUsersDto): Promise { - try { - const username = newUserData.email.split('@')[0]; - const createdUser: User = await prisma.user.create({ - data: { - email: newUserData.email, - name: newUserData.name, - isVerified: newUserData.isEmailVerified, - username, - phone: '', - gender: "MALE", - date_of_birth: new Date('2000-01-01'), - password_hash: '', - }, - }); - await prisma.patient.create({ - data: { - id: createdUser.id, - bc_address: '', - consent: false, - } - }); - return createdUser; - } catch (error) { - console.error("Error creating initial Google user profile:", error); - const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); - throw new HttpException(err.status, err.message, err.messageAr); - } + const username = newUserData.email.split('@')[0]; + const createdUser: User = await prisma.user.create({ + data: { + email: newUserData.email, + name: newUserData.name, + isVerified: newUserData.isEmailVerified, + username, + phone: '', + gender: "MALE", + date_of_birth: new Date('2000-01-01'), + password_hash: '', + }, + }); + await prisma.patient.create({ + data: { + id: createdUser.id, + bc_address: '', + consent: false, + } + }); + return createdUser; } public async updatePhoneNumber(userId: string, phone: string): Promise { - try { - await prisma.user.update({ - where: { id: userId }, - data: { phone }, - }); - } catch (error) { - console.error("Error updating phone number:", error); - const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); - throw new HttpException(err.status, err.message, err.messageAr); - } + await prisma.user.update({ + where: { id: userId }, + data: { phone }, + }); } public async getGoogleUserData(userId: string): Promise { - try { - const user: UserLoginData | null = await prisma.user.findUnique({ - where: { id: userId }, - select: { - email: true, - name: true, - username: true, - phone: true, - gender: true, - date_of_birth: true, - isVerified: true, - hasCompletedProfile: true, - } - }); - if (!user) { - const err = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); - throw new HttpException(err.status, err.message, err.messageAr); + const user: UserLoginData | null = await prisma.user.findUnique({ + where: { id: userId }, + select: { + email: true, + name: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + isVerified: true, + hasCompletedProfile: true, } - return user; - } catch (error) { - console.error("Error retrieving Google user data:", error); - const err = createBilingualError(500, ErrorMessages.SOMETHING_WENT_WRONG); + }); + if (!user) { + const err = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); throw new HttpException(err.status, err.message, err.messageAr); } + return user; } } \ No newline at end of file diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index bd3750b..9911aa8 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -43,22 +43,4 @@ export class IpfsService { // pin management --> TBD -} - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 8dc9433..0db491f 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -103,22 +103,4 @@ export class MedicalRecordService { // get a specific MR by id?? // handle permissions --> fabric stuff -} - - - - - - - - - - - - - - - - - - +} \ No newline at end of file From e55995d2a08682e58664fbb95bfecd0353cd2765 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 12 Dec 2025 23:04:28 +0200 Subject: [PATCH 047/210] Add Admin and Super Admin endpoints to Swagger documentation - Updated swagger-output.json to include new tags for Admin and Super Admin endpoints. - Added detailed API specifications for managing doctors and admins under Admin and Super Admin categories. - Enhanced existing Auth endpoints with additional parameters and response structures. --- src/routes/admin.route.ts | 284 ++---- src/routes/auth.route.ts | 233 ++++- src/routes/superAdmin.route.ts | 517 ++++------- src/swagger-output.json | 1539 +++++++++++++++++++++++++++++++- src/swagger.js | 3 + 5 files changed, 2019 insertions(+), 557 deletions(-) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 8a20640..038cb72 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -18,74 +18,42 @@ export class AdminRoute implements Routes { private initializeRoutes() { this.router.post( - `${this.path}/doctors`, - /* #swagger.tags = ['Admin'] - #swagger.summary = 'Add a new doctor' - #swagger.description = 'Admin endpoint to add a new doctor to the system' - #swagger.security = [{ "bearerAuth": []} ,{ cookieAuth: []}] - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ["email", "name", "phone", "gender","specialization"], - properties: { - email: { type: "string", format: "email", example: "doctor@example.com" }, - name: { type: "string", example: "John" }, - phone: { type: "string", example: "+1234567890" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - specialization: { type: "string", example: "CARDIOLOGY" } - } - } - } - } - } - #swagger.responses[201] = { - description: "Doctor added successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - email: { type: "string" }, - name: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - role: { type: "string" }, - date_of_birth: { type: "string", format: "date-time"}, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photo_url: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - }, - message: { type: "string", example: "Doctor added successfully" } - } - } - } - } - } - #swagger.responses[400] = { description: "Bad request - Invalid input data" } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/admin/doctors', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Doctor data', + required: true, + schema: { + $email: 'doctor@example.com', + $name: 'Dr. Smith', + $phone: '1234567890', + $gender: 'MALE or FEMALE', + $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[201] = { + description: 'Doctor added successfully', + schema: { + data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + message: 'Doctor added successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.ADMIN), @@ -95,65 +63,30 @@ export class AdminRoute implements Routes { ); this.router.get( - `${this.path}/doctors`, - /* #swagger.tags = ['Admin'] - #swagger.summary = 'Get all doctors' - #swagger.description = 'Retrieve a list of all doctors in the system' - #swagger.security = [{ "bearerAuth": [] } , { cookieAuth: [] }] - #swagger.parameters['accept-language'] = { - in: 'header', - description: 'Language preference (en or ar)', - required: false, - type: 'string', - example: 'en' - } - #swagger.responses[200] = { - description: "Doctors retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "array", - items: { - type: "object", - properties: { - name: { type: "string" }, - email: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - date_of_birth: { type: "string", format: "date-time" }, - role: { type: "string" }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photo_url: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - } - }, - message: { type: "string", example: "Doctors retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/admin/doctors', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Doctors retrieved successfully', + schema: { + data:[ { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], + message: 'Doctors retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.ADMIN), @@ -162,69 +95,36 @@ export class AdminRoute implements Routes { ); this.router.get( - `${this.path}/doctors/:id`, - /* #swagger.tags = ['Admin'] - #swagger.summary = 'Get doctor by ID' - #swagger.description = 'Retrieve a specific doctor\'s details by their ID' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.parameters['id'] = { - in: 'path', - description: 'Doctor ID', - required: true, - type: 'string' - } - #swagger.parameters['accept-language'] = { - in: 'header', - description: 'Language preference (en or ar)', - required: false, - type: 'string', - example: 'en' - } - #swagger.responses[200] = { - description: "Doctor retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - name: { type: "string" }, - email: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - date_of_birth: { type: "string", format: "date-time" }, - role: { type: "string" }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photo_url: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - }, - message: { type: "string", example: "Doctor retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } - #swagger.responses[404] = { description: "Doctor not found" } + '/admin/doctors/:id', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Doctor retrieved successfully', + schema: { + data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + message: 'Doctor retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.ADMIN), @@ -233,11 +133,11 @@ export class AdminRoute implements Routes { ); this.router.patch( - `${this.path}/doctors/:id`, + '/admin/doctors/:id', /* #swagger.tags = ['Admin'] */ AuthMiddleware, RoleMiddleware(Role.ADMIN), - this.adminController.updateDoctorVerificationStatus, + // this.adminController.updateDoctorVerificationStatus, ); } } \ No newline at end of file diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 221f0b2..e11bc33 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -21,35 +21,136 @@ export class AuthRoute implements Routes { private initializeRoutes() { this.router.post( `/auth/signup`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User signup data', + required: true, + schema: { + $email: 'user@example.com', + $name: 'John Doe', + $phone: '1234567890', + $password: 'password123' + } + } + #swagger.responses[201] = { + description: 'User successfully created', + schema: { + data: { + id: 1, + email: 'user@example.com', + name: 'John Doe', + phone: '1234567890', + isEmailVerified: false, + hasCompletedProfile: false, + gender: null, + date_of_birth: null, + role: 'PATIENT', + photoUrl: null + }, + message: 'Signed Up Successfully' + } + } + */ ValidationMiddleware(CreateUserDto), this.auth.signUp, ); this.router.post( `/auth/login`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User login data', + required: true, + schema: { + $emailOrUsername: 'user@example.com', + $password: 'password123', + rememberMe: false + } + } + #swagger.responses[200] = { + description: 'Login successful', + schema: { + data: { id: 1, email: 'user@example.com', name: 'John Doe' }, + message: 'Logged In Successfully' + } + } + */ ValidationMiddleware(LoginUserDto), this.auth.logIn, ); this.router.post( `/auth/logout`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Logout successful', + schema: { message: 'Logged Out Successfully' } + } + */ AuthMiddleware, this.auth.logOut, ); this.router.post( `/auth/refresh`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['RefreshToken'] = { + in: 'header', + description: 'Refresh token (sent via RefreshToken cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Token refreshed successfully', + schema: { + data: { user: {}, accessToken: { expiresIn: 3600, expiresAt: '2025-12-12T12:00:00.000Z' } }, + message: 'Token Refreshed Successfully' + } + } + */ AuthMiddleware, this.auth.refresh, ); this.router.patch( `/auth/complete-profile-info`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Complete user profile', + required: true, + schema: { + $gender: 'Male', + $date_of_birth: '1990-01-01' + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Profile completed successfully', + schema: { + data: { id: 1, hasCompletedProfile: true }, + message: 'Profile Completed Successfully' + } + } + */ ValidationMiddleware(CompleteUserProfileDto), AuthMiddleware, this.auth.completeProfile, @@ -57,46 +158,137 @@ export class AuthRoute implements Routes { this.router.patch( `/auth/verify-otp`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Verify OTP', + required: true, + schema: { $otp: '123456' } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'OTP verified successfully', + schema: { + data: true, + message: 'OTP Verified Successfully' + } + } + */ AuthMiddleware, this.auth.verifyOTP, ); this.router.post( `/auth/forget-password`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Request password reset', + required: true, + schema: { $email: 'user@example.com' } + } + #swagger.responses[200] = { + description: 'Password reset email sent', + schema: { message: 'Password Reset Email Sent Successfully' } + } + */ this.auth.forgetPassword, ); this.router.post( `/auth/reset-password`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Reset password', + required: true, + schema: { + $token: 'reset-token', + $newPassword: 'newPassword123' + } + } + #swagger.responses[200] = { + description: 'Password reset successfully', + schema: { message: 'Password Reset Successfully' } + } + */ ValidationMiddleware(ResetPasswordDto), this.auth.resetPassword, ); this.router.post( `/auth/resend-otp`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'OTP resent successfully', + schema: { message: 'OTP Resent Successfully' } + } + */ AuthMiddleware, this.auth.resendOTP, ); this.router.get( `/auth/google`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.responses[302] = { + description: 'Redirects to Google OAuth consent page' + } + */ this.googleAuth.googleOAuth, ); this.router.get( `/auth/google/callback`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.responses[302] = { + description: 'Redirects after Google authentication' + } + */ this.googleAuth.googleOAuthCallback, ); this.router.patch( `/auth/google/update-phone`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Update Google user phone', + required: true, + schema: { $phone: '1234567890' } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Phone number updated successfully', + schema: { + data: { phone: '1234567890' }, + message: 'Phone number updated successfully' + } + } + */ ValidationMiddleware(UpdateGoogleUserPhoneDto), AuthMiddleware, this.googleAuth.updatePhoneNumber, @@ -104,7 +296,22 @@ export class AuthRoute implements Routes { this.router.get( `/auth/google/userData`, - /* #swagger.tags = ['Auth'] */ + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'User data retrieved successfully', + schema: { + data: { email: 'user@example.com', name: 'John Doe', username: 'johndoe', phone: '1234567890', gender: 'MALE' , date_of_birth: '1990-01-01', isVerified: false, hasCompletedProfile: false }, + message: 'User data retrieved successfully' + } + } + */ AuthMiddleware, this.googleAuth.getGoogleUserData, ); diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index a3a1500..42031f1 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -23,107 +23,81 @@ export class SuperAdminRoute implements Routes { // ADMIN ROUTES this.router.post( - `${this.path}/admins`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Add a new admin' - #swagger.description = 'Super admin endpoint to add a new admin to the system' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ["email", "name", "password", "phone", "gender", "date_of_birth"], - properties: { - email: { type: "string", format: "email", example: "admin@example.com" }, - name: { type: "string", example: "Jane Smith" }, - password: { type: "string", format: "password", example: "SecurePass123!" }, - phone: { type: "string", example: "+1234567890" }, - gender: { type: "string", enum: ["MALE", "FEMALE"], example: "FEMALE" }, - date_of_birth: { type: "string", format: "date", example: "1990-01-01" } - } - } - } - } - } - #swagger.responses[201] = { - description: "Admin added successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - email: { type: "string" }, - name: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - role: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - date_of_birth: { type: "string", format: "date-time" }, - photo_url: { type: "string", nullable: true } - } - }, - message: { type: "string", example: "Admin added successfully" } - } - } - } - } - } - #swagger.responses[400] = { description: "Bad request - Invalid input data" } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/super-admin/admins', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Admin data', + required: true, + schema: { + $email: 'admin@example.com', + $name: 'Jane Smith', + $password: 'SecurePass123!', + $phone: '1234567890', + $gender: 'MALE or FEMALE', + $date_of_birth: '1990-01-01' + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[201] = { + description: 'Admin added successfully', + schema: { + data: { + email: 'admin@example.com', + name: 'Jane Smith', + role: 'ADMIN', + username: 'janesmith', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1990-01-01T00:00:00.000Z', + photo_url: null, + isVerified: true, + hasCompletedProfile: true + }, + message: 'Admin added successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), ValidationMiddleware(AddAdminFromSuperAdminDto), this.superAdminController.addAdmin, ); - + this.router.get( - `${this.path}/admins`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Get all admins' - #swagger.description = 'Retrieve a list of all admins in the system' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.responses[200] = { - description: "Admins retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "array", - items: { - type: "object", - properties: { - email: { type: "string" }, - name: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - role: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - date_of_birth: { type: "string", format: "date-time" }, - photo_url: { type: "string", nullable: true } - } - } - }, - message: { type: "string", example: "Admins retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/super-admin/admins', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Admins retrieved successfully', + schema: { + data: [{ + email: 'admin@example.com', + name: 'Jane Smith', + role: 'ADMIN', + username: 'janesmith', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1990-01-01T00:00:00.000Z', + photo_url: null, + isVerified: true, + hasCompletedProfile: true + }], + message: 'Admins retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), @@ -131,48 +105,39 @@ export class SuperAdminRoute implements Routes { ); this.router.get( - `${this.path}/admins/:id`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Get admin by ID' - #swagger.description = 'Retrieve a specific admin\'s details by their ID' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.parameters['id'] = { - in: 'path', - description: 'Admin ID', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: "Admin retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - email: { type: "string" }, - name: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - role: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - date_of_birth: { type: "string", format: "date-time" }, - photo_url: { type: "string", nullable: true } - } - }, - message: { type: "string", example: "Admin retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } - #swagger.responses[404] = { description: "Admin not found" } + '/super-admin/admins/:id', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Admin ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Admin retrieved successfully', + schema: { + data: { + email: 'admin@example.com', + name: 'Jane Smith', + role: 'ADMIN', + username: 'janesmith', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1990-01-01T00:00:00.000Z', + photo_url: null, + isVerified: true, + hasCompletedProfile: true + }, + message: 'Admin retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), @@ -181,74 +146,42 @@ export class SuperAdminRoute implements Routes { // DOCTOR ROUTES this.router.post( - `${this.path}/doctors`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Add a new doctor' - #swagger.description = 'Super admin endpoint to add a new doctor to the system' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ["email", "name", "phone", "gender", "specialization"], - properties: { - email: { type: "string", format: "email", example: "doctor@example.com" }, - name: { type: "string", example: "John Doe" }, - phone: { type: "string", example: "+1234567890" }, - gender: { type: "string", enum: ["MALE", "FEMALE"], example: "MALE" }, - specialization: { type: "string", example: "CARDIOLOGY" } - } - } - } - } - } - #swagger.responses[201] = { - description: "Doctor added successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - email: { type: "string" }, - name: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string" }, - role: { type: "string" }, - date_of_birth: { type: "string", format: "date-time" }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photoUrl: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - }, - message: { type: "string", example: "Doctor added successfully" } - } - } - } - } - } - #swagger.responses[400] = { description: "Bad request - Invalid input data" } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/super-admin/doctors', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Doctor data', + required: true, + schema: { + $email: 'doctor@example.com', + $name: 'Dr. John Doe', + $phone: '1234567890', + $gender: 'MALE or FEMALE', + $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[201] = { + description: 'Doctor added successfully', + schema: { + data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + message: 'Doctor added successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), @@ -258,65 +191,30 @@ export class SuperAdminRoute implements Routes { ); this.router.get( - `${this.path}/doctors`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Get all doctors' - #swagger.description = 'Retrieve a list of all doctors in the system' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.parameters['accept-language'] = { - in: 'header', - description: 'Language preference (en or ar)', - required: false, - type: 'string', - example: 'en' - } - #swagger.responses[200] = { - description: "Doctors retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "array", - items: { - type: "object", - properties: { - name: { type: "string" }, - email: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - date_of_birth: { type: "string", format: "date-time" }, - role: { type: "string" }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photo_url: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - } - }, - message: { type: "string", example: "Doctors retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } + '/super-admin/doctors', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Doctors retrieved successfully', + schema: { + data: [{ email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], + message: 'Doctors retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), @@ -325,69 +223,36 @@ export class SuperAdminRoute implements Routes { ); this.router.get( - `${this.path}/doctors/:id`, - /* #swagger.tags = ['Super Admin'] - #swagger.summary = 'Get doctor by ID' - #swagger.description = 'Retrieve a specific doctor\'s details by their ID' - #swagger.security = [{ "bearerAuth": [] }, { cookieAuth: [] }] - #swagger.parameters['id'] = { - in: 'path', - description: 'Doctor ID', - required: true, - type: 'string' - } - #swagger.parameters['accept-language'] = { - in: 'header', - description: 'Language preference (en or ar)', - required: false, - type: 'string', - example: 'en' - } - #swagger.responses[200] = { - description: "Doctor retrieved successfully", - content: { - "application/json": { - schema: { - type: "object", - properties: { - data: { - type: "object", - properties: { - name: { type: "string" }, - email: { type: "string" }, - username: { type: "string" }, - phone: { type: "string" }, - gender: { type: "string", enum: ["MALE", "FEMALE"] }, - date_of_birth: { type: "string", format: "date-time" }, - role: { type: "string" }, - isVerified: { type: "boolean" }, - hasCompletedProfile: { type: "boolean" }, - photo_url: { type: "string", nullable: true }, - doctor: { - type: "object", - nullable: true, - properties: { - specialization: { - type: "object", - properties: { - key: { type: "string" }, - value: { type: "string" } - } - }, - avg_time: { type: "number", nullable: true } - } - } - } - }, - message: { type: "string", example: "Doctor retrieved successfully" } - } - } - } - } - } - #swagger.responses[401] = { description: "Unauthorized - Invalid or missing token" } - #swagger.responses[403] = { description: "Forbidden - Insufficient permissions" } - #swagger.responses[404] = { description: "Doctor not found" } + '/super-admin/doctors/:id', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Doctor retrieved successfully', + schema: { + data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + message: 'Doctor retrieved successfully' + } + } */ AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), diff --git a/src/swagger-output.json b/src/swagger-output.json index ff85374..0e99589 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -12,6 +12,14 @@ "name": "Auth", "description": "Authentication and account endpoints" }, + { + "name": "Admin", + "description": "Admin endpoints" + }, + { + "name": "Super Admin", + "description": "Super Admin endpoints" + }, { "name": "MedicalRecords", "description": "Hyperledger Fabric medical record endpoints" @@ -27,9 +35,92 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User signup data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "password": { + "type": "string", + "example": "password123" + } + }, + "required": [ + "email", + "name", + "phone", + "password" + ] + } + } + ], "responses": { - "default": { - "description": "" + "201": { + "description": "User successfully created", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "isEmailVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "gender": {}, + "date_of_birth": {}, + "role": { + "type": "string", + "example": "PATIENT" + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Signed Up Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -40,9 +131,67 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "user@example.com" + }, + "password": { + "type": "string", + "example": "password123" + }, + "rememberMe": { + "type": "boolean", + "example": false + } + }, + "required": [ + "emailOrUsername", + "password" + ] + } + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Login successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + } + } + }, + "message": { + "type": "string", + "example": "Logged In Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -53,9 +202,30 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Logout successful", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Logged Out Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -66,9 +236,52 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "RefreshToken", + "in": "header", + "description": "Refresh token (sent via RefreshToken cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Token refreshed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": {} + }, + "accessToken": { + "type": "object", + "properties": { + "expiresIn": { + "type": "number", + "example": 3600 + }, + "expiresAt": { + "type": "string", + "example": "2025-12-12T12:00:00.000Z" + } + } + } + } + }, + "message": { + "type": "string", + "example": "Token Refreshed Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -79,9 +292,66 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Complete user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "gender": { + "type": "string", + "example": "Male" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + } + }, + "required": [ + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Profile completed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Profile Completed Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -92,9 +362,52 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Verify OTP", + "required": true, + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "example": "123456" + } + }, + "required": [ + "otp" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "OTP verified successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "OTP Verified Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -105,9 +418,41 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Request password reset", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + } + }, + "required": [ + "email" + ] + } + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Password reset email sent", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password Reset Email Sent Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -118,9 +463,46 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Reset password", + "required": true, + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "reset-token" + }, + "newPassword": { + "type": "string", + "example": "newPassword123" + } + }, + "required": [ + "token", + "newPassword" + ] + } + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Password reset successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password Reset Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -131,9 +513,30 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "OTP resent successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "OTP Resent Successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -145,8 +548,8 @@ ], "description": "", "responses": { - "default": { - "description": "" + "302": { + "description": "Redirects to Google OAuth consent page" } } } @@ -158,8 +561,8 @@ ], "description": "", "responses": { - "default": { - "description": "" + "302": { + "description": "Redirects after Google authentication" } } } @@ -170,9 +573,57 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Update Google user phone", + "required": true, + "schema": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "example": "1234567890" + } + }, + "required": [ + "phone" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "Phone number updated successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "example": "1234567890" + } + } + }, + "message": { + "type": "string", + "example": "Phone number updated successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -183,9 +634,67 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], "responses": { - "default": { - "description": "" + "200": { + "description": "User data retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "username": { + "type": "string", + "example": "johndoe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + } + } + }, + "message": { + "type": "string", + "example": "User data retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } } } } @@ -266,6 +775,984 @@ } } } + }, + "/admin/doctors": { + "post": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "specialization" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Doctor added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + } + }, + "message": { + "type": "string", + "example": "Doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/doctors/{id}": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "patch": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/super-admin/admins": { + "post": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Admin data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "password": { + "type": "string", + "example": "SecurePass123!" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + } + }, + "required": [ + "email", + "name", + "password", + "phone", + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Admin added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Admin added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Admins retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + } + }, + "message": { + "type": "string", + "example": "Admins retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/admins/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Admin ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Admin retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Admin retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/doctors": { + "post": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "specialization" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Doctor added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + } + }, + "message": { + "type": "string", + "example": "Doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/doctors/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index dd36c01..cd26d75 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -9,8 +9,11 @@ const doc = { schemes: ['http'], tags: [ { name: 'Auth', description: 'Authentication and account endpoints' }, + { name: 'Admin', description: 'Admin endpoints' }, + { name: 'Super Admin', description: 'Super Admin endpoints' }, { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, ], + }; const outputFile = './swagger-output.json'; From b1c366cb37f979f98d74272caa2b3d9a446f6f3f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 12 Dec 2025 23:12:19 +0200 Subject: [PATCH 048/210] added admin verify doctor status endpoint --- src/controllers/admin.controller.ts | 9 +++++++++ src/routes/admin.route.ts | 2 +- src/services/admin.service.ts | 14 ++++++++++++++ src/services/superAdmin.service.ts | 8 ++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index cb4be03..712dcdb 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -76,4 +76,13 @@ export class AdminController { }); } + public updateDoctorVerificationStatus = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const doctorId = req.params.id; + const { isVerified } = req.body; + await this.adminService.updateDoctorVerificationStatus(doctorId, isVerified); + res.status(200).json({ + message: 'Doctor verification status updated successfully' + }); + } } \ No newline at end of file diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 038cb72..4ed46e5 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -137,7 +137,7 @@ export class AdminRoute implements Routes { /* #swagger.tags = ['Admin'] */ AuthMiddleware, RoleMiddleware(Role.ADMIN), - // this.adminController.updateDoctorVerificationStatus, + this.adminController.updateDoctorVerificationStatus, ); } } \ No newline at end of file diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index da43d81..44a28e3 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -146,4 +146,18 @@ export class AdminService { return doctor; } + public async updateDoctorVerificationStatus(doctorId: string, isVerified: boolean): Promise { + + const doctor = await prisma.user.findUnique({ + where: { id: doctorId, role: Role.DOCTOR }, + }); + if (!doctor) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + await prisma.user.update({ + where: { id: doctorId }, + data: { isVerified }, + }); + } } diff --git a/src/services/superAdmin.service.ts b/src/services/superAdmin.service.ts index 43c176e..5cf80df 100644 --- a/src/services/superAdmin.service.ts +++ b/src/services/superAdmin.service.ts @@ -1,5 +1,7 @@ import { AddAdminFromSuperAdminDto, AdminFromSuperAdminResponseDto } from "@/dtos/superAdmins.dto"; +import { HttpException } from "@/exceptions/HttpException"; import { User } from "@/interfaces"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { PrismaClient, Role } from "@prisma/client"; import { hash } from "bcrypt"; import { Service } from "typedi"; @@ -16,7 +18,8 @@ export class SuperAdminService { where: { email: adminData.email } }); if (existingUser) { - throw new Error('Email already exists'); + const err = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(err.status, err.message, err.messageAr); } const username = adminData.email.split('@')[0]; @@ -25,7 +28,8 @@ export class SuperAdminService { where: { username } }); if (existingUsername) { - throw new Error('Username already exists'); + const err = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(err.status, err.message, err.messageAr); } const hashedPassword = await hash(adminData.password, 10); From 7e66f9b6249b41a95bab2847782bc388415d6dac Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 12 Dec 2025 23:13:18 +0200 Subject: [PATCH 049/210] feat: Add grant access functionality and enhance error handling in Fabric service --- src/controllers/fabric.controller.ts | 40 +++---- src/interfaces/medical-records.interface.ts | 2 + src/middlewares/error.middleware.ts | 98 +++++++++++++++-- src/routes/fabric.route.ts | 22 ++++ src/services/fabric.service.ts | 113 +++++++++++++++----- src/swagger-output.json | 105 +++++++++++++++++- 6 files changed, 323 insertions(+), 57 deletions(-) diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts index 5ce8225..6d9f423 100644 --- a/src/controllers/fabric.controller.ts +++ b/src/controllers/fabric.controller.ts @@ -7,9 +7,7 @@ import { HttpException } from '@/exceptions/HttpException'; class FabricController { public fabricService = new FabricService(); - /** - * Extract identity label from request header - */ + private getIdentityLabel(req: Request): string { const identityLabel = req.headers['x-fabric-identity'] as string; if (!identityLabel) { @@ -18,10 +16,7 @@ class FabricController { return identityLabel; } - /** - * Onboard a new organization identity - * POST /fabric/onboard - */ + public onboardIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { try { const input: FabricIdentityInput = req.body; @@ -43,10 +38,7 @@ class FabricController { } }; - /** - * List all stored identities (without sensitive data) - * GET /fabric/identities - */ + public listIdentities = async (req: Request, res: Response, next: NextFunction): Promise => { try { const identities = await identityStorage.listIdentities(); @@ -56,10 +48,7 @@ class FabricController { } }; - /** - * Delete an identity - * DELETE /fabric/identities/:label - */ + public deleteIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { try { const label = req.params.label; @@ -76,10 +65,6 @@ class FabricController { } }; - /** - * Get connection statistics - * GET /fabric/connections - */ public getConnectionStats = async (req: Request, res: Response, next: NextFunction): Promise => { try { const stats = this.fabricService.getConnectionStats(); @@ -131,6 +116,23 @@ class FabricController { } }; + public grantAccess = async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const identityLabel = this.getIdentityLabel(req); + const patientId = req.params.patientId; + const { targetMsp } = req.body; + + if (!targetMsp) { + throw new HttpException(400, 'targetMsp is required'); + } + + await this.fabricService.grantAccess(identityLabel, patientId, targetMsp); + res.status(200).json({ message: 'Access granted successfully' }); + } catch (error) { + next(error); + } + }; + public initLedger = async (req: Request, res: Response, next: NextFunction): Promise => { try { const identityLabel = this.getIdentityLabel(req); diff --git a/src/interfaces/medical-records.interface.ts b/src/interfaces/medical-records.interface.ts index f18d4f3..9eb8113 100644 --- a/src/interfaces/medical-records.interface.ts +++ b/src/interfaces/medical-records.interface.ts @@ -7,4 +7,6 @@ export interface MedicalRecord { bloodType: string; ipfsCid: string; summary?: string; + ownerMsp?: string; + authorizedMsps?: string[]; } diff --git a/src/middlewares/error.middleware.ts b/src/middlewares/error.middleware.ts index a9758eb..ff77a37 100644 --- a/src/middlewares/error.middleware.ts +++ b/src/middlewares/error.middleware.ts @@ -2,18 +2,98 @@ import { NextFunction, Request, Response } from 'express'; import { HttpException } from '@exceptions/HttpException'; import { logger } from '@utils/logger'; -export const ErrorMiddleware = (error: HttpException, req: Request, res: Response, next: NextFunction) => { +function mapGrpcCodeToHttp(code: number): number { + switch (code) { + case 0: // OK + return 200; + case 1: // CANCELLED + return 499; + case 3: // INVALID_ARGUMENT + return 400; + case 4: // DEADLINE_EXCEEDED + return 504; + case 5: // NOT_FOUND + return 404; + case 6: // ALREADY_EXISTS + return 409; + case 7: // PERMISSION_DENIED + return 403; + case 8: // RESOURCE_EXHAUSTED + return 429; + case 9: // FAILED_PRECONDITION + return 412; + case 10: // ABORTED + return 409; + case 11: // OUT_OF_RANGE + return 400; + case 12: // UNIMPLEMENTED + return 501; + case 13: // INTERNAL + return 500; + case 14: // UNAVAILABLE + return 503; + case 15: // DATA_LOSS + return 500; + case 16: // UNAUTHENTICATED + return 401; + default: + return 500; + } +} + +export const ErrorMiddleware = (error: any, req: Request, res: Response, next: NextFunction) => { try { - const status: number = error.status || 500; - const message: string = error.message || 'Something went wrong'; - const messageAr: string = error.messageAr || 'حدث خطأ ما'; + let status = 500; + let message = 'Something went wrong'; + let messageAr = 'حدث خطأ ما'; + + // Preserve HttpException + if (error instanceof HttpException) { + status = error.status || 500; + message = error.message || message; + messageAr = error.messageAr || messageAr; + } else { + // Generic Error handling: try to map gRPC/Fabric errors to HTTP codes + message = error?.message || String(error); + + // Prefer numeric code property if available + let grpcCode: number | null = null; + if (typeof error?.code === 'number') grpcCode = error.code; + else if (typeof error?.status === 'number') grpcCode = error.status; + + // Try to parse numeric code from message like "status code 10" or "code: 9" or leading "9 FAILED_PRECONDITION" + if (grpcCode === null) { + const m1 = /status code\s*[:=]?\s*(\d+)/i.exec(message); + const m2 = /code\s*[:=]?\s*(\d+)/i.exec(message); + const m3 = /^\s*(\d+)\s+[A-Z_]+/i.exec(message); + const m = m1 || m2 || m3; + if (m) { + const parsed = parseInt(m[1], 10); + if (!isNaN(parsed)) grpcCode = parsed; + } + } + + if (grpcCode !== null) { + status = mapGrpcCodeToHttp(grpcCode); + } else { + // Fallback mapping from tokens + const t = (message || '').toUpperCase(); + if (t.includes('UNAUTHENTICATED')) status = 401; + else if (t.includes('PERMISSION_DENIED')) status = 403; + else if (t.includes('ENDORSEMENT_POLICY') || t.includes('FAILED_PRECONDITION')) status = 412; + else if (t.includes('NOT_FOUND')) status = 404; + else if (t.includes('ALREADY_EXISTS')) status = 409; + else if (t.includes('INVALID_ARGUMENT')) status = 400; + else status = 500; + } + } logger.error(`[${req.method}] ${req.path} >> StatusCode:: ${status}, Message:: ${message}`); - res.status(status).json({ - messageEn : message, - messageAr + res.status(status).json({ + messageEn: message, + messageAr, }); - } catch (error) { - next(error); + } catch (err) { + next(err); } }; diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index 6ff12ff..d7460bf 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -160,6 +160,28 @@ export class FabricRoute implements Routes { ValidationMiddleware(UpdateMedicalRecordDto), this.fabricController.updateRecord, ); + + this.router.post( + '/records/:patientId/access', + /* + #swagger.tags = ['MedicalRecords'] + #swagger.parameters['X-Fabric-Identity'] = { + in: 'header', + description: 'Identity label (e.g., org1)', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Grant access to MSP', + required: true, + schema: { + $targetMsp: 'Org2MSP' + } + } + */ + this.fabricController.grantAccess, + ); } } diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index e24a0e9..0ebdb54 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -116,27 +116,79 @@ class FabricService { } public async addRecord(identityLabel: string, payload: MedicalRecord): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); + const { contract, identity } = await this.getGatewayConnection(identityLabel); console.log(`\n--> Submit Transaction: AddRecord (${identityLabel})`); - await contract.submitTransaction( - 'AddRecord', - payload.patientId, - payload.firstName, - payload.lastName, - payload.dateOfBirth, - payload.gender, - payload.bloodType, - payload.ipfsCid, - '', - ); + + await contract.submit('AddRecord', { + arguments: [ + payload.patientId, + payload.firstName, + payload.lastName, + payload.dateOfBirth, + payload.gender, + payload.bloodType, + payload.summary || '', + ], + transientData: { + ipfsCid: Buffer.from(payload.ipfsCid) + }, + endorsingOrganizations: [identity.mspId], + }); } public async getRecordByPatientId(identityLabel: string, patientId: string): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); + const { contract, identity } = await this.getGatewayConnection(identityLabel); console.log(`\n--> Evaluate Transaction: GetRecord (${identityLabel})`); - const resultBytes = await contract.evaluateTransaction('GetRecord', patientId); - const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson) as MedicalRecord; + + // First, fetch the public metadata so we can determine the Owner MSP for this record. + // We don't have a separate "GetRecordPublic" chaincode function, so reuse GetAllRecords + // and find the single entry. For large datasets consider adding a light-weight metadata accessor. + const allBytes = await contract.evaluateTransaction('GetAllRecords'); + const allJson = this.utf8Decoder.decode(allBytes); + const allRecords = JSON.parse(allJson) as MedicalRecord[]; + + const publicRecord = allRecords.find(r => r.patientId === patientId); + if (!publicRecord) { + throw new HttpException(404, `Record not found: ${patientId}`); + } + + const ownerMsp = publicRecord.ownerMsp || publicRecord.ownerMsp?.toString(); + + // If caller is the owner, perform a normal evaluate (owner peer will have private data). + // If caller is NOT the owner, we must ensure the proposal is evaluated on the owner's peers + // so they can read their implicit private data collection. We instruct the gateway to target + // the owner's organizations for endorsement. + const callerMsp = identity.mspId; + + try { + if (callerMsp === ownerMsp) { + const resultBytes = await contract.evaluateTransaction('GetRecord', patientId); + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson) as MedicalRecord; + } + + // Non-owner: request evaluation targeted at owner's org so that owner's peer can access private data. + const resultBytes = await contract.evaluate('GetRecord', { + arguments: [patientId], + endorsingOrganizations: [ownerMsp], + }); + + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson) as MedicalRecord; + } catch (err: any) { + // Surface clearer error when access is denied + const msg = err?.message || String(err); + if (msg.toLowerCase().includes('not authorized') || msg.toLowerCase().includes('not authorized to access')) { + throw new HttpException(403, `Access denied for ${identityLabel} to record ${patientId}`, msg); + } + throw err; + } + } + + public async grantAccess(identityLabel: string, patientId: string, targetMsp: string): Promise { + const { contract } = await this.getGatewayConnection(identityLabel); + console.log(`\n--> Submit Transaction: GrantAccess (${identityLabel})`); + await contract.submitTransaction('GrantAccess', patientId, targetMsp); } @@ -145,19 +197,24 @@ class FabricService { patientId: string, payload: Omit ): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); + const { contract, identity } = await this.getGatewayConnection(identityLabel); console.log(`\n--> Submit Transaction: UpdateRecord (${identityLabel})`); - await contract.submitTransaction( - 'UpdateRecord', - patientId, - payload.firstName, - payload.lastName, - payload.dateOfBirth, - payload.gender, - payload.bloodType, - payload.ipfsCid, - '', - ); + + await contract.submit('UpdateRecord', { + arguments: [ + patientId, + payload.firstName, + payload.lastName, + payload.dateOfBirth, + payload.gender, + payload.bloodType, + payload.summary || '', + ], + transientData: { + ipfsCid: Buffer.from(payload.ipfsCid) + }, + endorsingOrganizations: [identity.mspId], + }); } public async closeConnection(identityLabel: string): Promise { diff --git a/src/swagger-output.json b/src/swagger-output.json index eb9e93c..3387846 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -117,6 +117,15 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token or cookie (e.g. Authorization: Bearer )", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -130,6 +139,15 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Refresh token in cookie or Authorization header. If using cookie, ensure cookies are sent.", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -166,6 +184,13 @@ "date_of_birth" ] } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" } ], "responses": { @@ -182,6 +207,13 @@ ], "description": "", "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" + }, { "name": "body", "in": "body", @@ -285,6 +317,15 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -342,6 +383,13 @@ "phone" ] } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" } ], "responses": { @@ -357,6 +405,15 @@ "Auth" ], "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -413,7 +470,7 @@ }, "chaincodeName": { "type": "string", - "example": "basic" + "example": "test" } }, "required": [ @@ -711,6 +768,52 @@ } } } + }, + "/records/{patientId}/access": { + "post": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Grant access to MSP", + "required": true, + "schema": { + "type": "object", + "properties": { + "targetMsp": { + "type": "string", + "example": "Org2MSP" + } + }, + "required": [ + "targetMsp" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } } } } \ No newline at end of file From edf86cac060db82cab422457fa9e63b40b76fd4a Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 12 Dec 2025 23:37:31 +0200 Subject: [PATCH 050/210] added verify doctor status endpoint --- src/controllers/admin.controller.ts | 21 +++++++++++++++++++++ src/dtos/admins.dto.ts | 4 ++-- src/routes/admin.route.ts | 27 ++++++++++++++++++--------- src/routes/doctors.route.ts | 14 ++++++++++++++ src/routes/superAdmin.route.ts | 3 ++- src/services/admin.service.ts | 26 ++++++++++++++++++++++++++ src/services/superAdmin.service.ts | 1 + 7 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/routes/doctors.route.ts diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 712dcdb..30bc479 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -76,6 +76,27 @@ export class AdminController { }); } + public getUnverifiedDoctors = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const unverifiedDoctors = await this.adminService.getUnverifiedDoctors(); + const language = req.language; + // Format specializations based on language preference + const formattedDoctors = unverifiedDoctors.map(doctor => ({ + ...doctor, + doctor: doctor.doctor ? { + ...doctor.doctor, + specialization: formatSpecializationResponse( + doctor.doctor.specialization as SpecializationKey, + language + ), + } : null, + })); + res.status(200).json({ + data: formattedDoctors, + message: 'Unverified doctors retrieved successfully' + }); + } + public updateDoctorVerificationStatus = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { const doctorId = req.params.id; diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 00e9297..50941dc 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -35,9 +35,9 @@ export class DoctorFromAdminResponseDto { public phone: string; public gender: Gender; public date_of_birth: Date; - public role: Role; + public role?: Role; public isVerified: boolean; - public hasCompletedProfile: boolean + public hasCompletedProfile?: boolean public photoUrl?: string; public doctor?: { specialization: string; diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 4ed46e5..d71828f 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -81,7 +81,7 @@ export class AdminRoute implements Routes { #swagger.responses[200] = { description: 'Doctors retrieved successfully', schema: { - data:[ { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], message: 'Doctors retrieved successfully' @@ -94,6 +94,23 @@ export class AdminRoute implements Routes { this.adminController.getAllDoctors, ); + this.router.get( + '/admin/doctors/unverified', + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + LanguageMiddleware, + this.adminController.getUnverifiedDoctors, + ); + + this.router.patch( + '/admin/doctors/verify/:id', + /* #swagger.tags = ['Admin'] */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.updateDoctorVerificationStatus, + ); + this.router.get( '/admin/doctors/:id', /* @@ -131,13 +148,5 @@ export class AdminRoute implements Routes { LanguageMiddleware, this.adminController.getDoctorById, ); - - this.router.patch( - '/admin/doctors/:id', - /* #swagger.tags = ['Admin'] */ - AuthMiddleware, - RoleMiddleware(Role.ADMIN), - this.adminController.updateDoctorVerificationStatus, - ); } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts new file mode 100644 index 0000000..55e2dde --- /dev/null +++ b/src/routes/doctors.route.ts @@ -0,0 +1,14 @@ + + +export class DoctorsRoute implements Routes { + public path = '/doctors' + public router = Router(); + public doctorsController = new DoctorsController(); + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.post( + `${this.path}/:doctorId/verify`, + /* #swagger.tags = ['Admin'] */ \ No newline at end of file diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 42031f1..1536949 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -84,6 +84,7 @@ export class SuperAdminRoute implements Routes { description: 'Admins retrieved successfully', schema: { data: [{ + id: '1', email: 'admin@example.com', name: 'Jane Smith', role: 'ADMIN', @@ -209,7 +210,7 @@ export class SuperAdminRoute implements Routes { #swagger.responses[200] = { description: 'Doctors retrieved successfully', schema: { - data: [{ email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', + data: [{ id: '1', email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], message: 'Doctors retrieved successfully' diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 44a28e3..7d8af6a 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -90,6 +90,7 @@ export class AdminService { const doctors = await prisma.user.findMany({ where: { role: Role.DOCTOR }, select: { + id: true, name: true, email: true, username: true, @@ -146,6 +147,31 @@ export class AdminService { return doctor; } + public async getUnverifiedDoctors(): Promise { + + const unverifiedDoctors = await prisma.user.findMany({ + where: { role: Role.DOCTOR, isVerified: false }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + isVerified: true, + photo_url: true, + doctor: { + select: { + specialization: true, + avg_time: true + } + }, + }, + }); + return unverifiedDoctors + + } public async updateDoctorVerificationStatus(doctorId: string, isVerified: boolean): Promise { const doctor = await prisma.user.findUnique({ diff --git a/src/services/superAdmin.service.ts b/src/services/superAdmin.service.ts index 5cf80df..1949628 100644 --- a/src/services/superAdmin.service.ts +++ b/src/services/superAdmin.service.ts @@ -67,6 +67,7 @@ export class SuperAdminService { const admins = await prisma.user.findMany({ where: { role: Role.ADMIN }, select: { + id: true, email: true, name: true, username: true, From 254094143237405e4a098ca64010adab21575dfd Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 00:07:44 +0200 Subject: [PATCH 051/210] added doctor controller --- src/controllers/admin.controller.ts | 6 ++- src/controllers/doctor.controller.ts | 17 ++++++++ src/dtos/doctors.dto.ts | 35 +++++++++++++++ src/routes/doctors.route.ts | 15 +++++-- src/services/admin.service.ts | 21 +++++---- src/services/doctor.service.ts | 64 ++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 src/controllers/doctor.controller.ts create mode 100644 src/dtos/doctors.dto.ts create mode 100644 src/services/doctor.service.ts diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 30bc479..9622af3 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -22,8 +22,12 @@ export class AdminController { ), } : null; + const doctorResponse = { + ...newDoctor, + doctor: formattedNewDoctor, + }; res.status(201).json({ - data: formattedNewDoctor, + data: doctorResponse, message: 'Doctor added successfully' }); }; diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts new file mode 100644 index 0000000..60f4eb8 --- /dev/null +++ b/src/controllers/doctor.controller.ts @@ -0,0 +1,17 @@ + +import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { RequestWithLanguage } from "@/middlewares/language.middleware"; +import { DoctorService } from "@/services/doctor.service"; +import { NextFunction, Request, Response } from "express"; +import { Container } from "typedi"; + + +export class DoctorController { + public doctorService = Container.get(DoctorService); + + public doctorSignup = async (req: RequestWithLanguage, res: Response, next: NextFunction) => { + const doctorData: DoctorSignupRequestDto = req.body; + await this.doctorService.signup(doctorData); + res.status(201).json({ message: 'Doctor signed up successfully' }); + }; +} \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts new file mode 100644 index 0000000..f4f319c --- /dev/null +++ b/src/dtos/doctors.dto.ts @@ -0,0 +1,35 @@ +import { TransformSpecialization } from "@/utils/specializationTransform"; +import { IsValidSpecialization } from "@/validators/specialization.validator"; +import { Gender } from "@prisma/client"; +import { IsString, IsNotEmpty, IsEmail } from "class-validator"; + +export class DoctorSignupRequestDto { + @IsString() + @IsNotEmpty() + public name: string; + + @IsEmail() + @IsNotEmpty() + public email: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsString() + @IsNotEmpty() + public password: string; + + @IsString() + @IsNotEmpty() + public gender: Gender; + + @IsString() + public date_of_birth?: Date; + + @TransformSpecialization() // Converts EN/AR to key before validation + @IsValidSpecialization({ + message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' + }) + public specialization: string; +} \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 55e2dde..dd631d6 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,14 +1,23 @@ +import { DoctorController } from "@/controllers/doctor.controller"; +import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { Routes } from "@/interfaces"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Router } from "express"; export class DoctorsRoute implements Routes { public path = '/doctors' public router = Router(); - public doctorsController = new DoctorsController(); + public doctorsController = new DoctorController(); constructor() { this.initializeRoutes(); } private initializeRoutes() { this.router.post( - `${this.path}/:doctorId/verify`, - /* #swagger.tags = ['Admin'] */ \ No newline at end of file + `/doctors/signup`, + ValidationMiddleware(DoctorSignupRequestDto), + this.doctorsController.doctorSignup + ); + } +} \ No newline at end of file diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 7d8af6a..e159813 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -53,6 +53,17 @@ export class AdminService { isVerified: true, hasCompletedProfile: false, }, + }); + + // Create doctor profile + await prisma.doctor.create({ + data: { + id: createdUser.id, + specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") + } + }); + const createdDoctor = await prisma.user.findUnique({ + where: { id: createdUser.id }, select: { id: true, name: true, @@ -72,15 +83,7 @@ export class AdminService { }, } }); - - // Create doctor profile - await prisma.doctor.create({ - data: { - id: createdUser.id, - specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") - } - }); - const { id, ...createdDoctor } = createdUser; + return createdDoctor; } diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts new file mode 100644 index 0000000..42af9ee --- /dev/null +++ b/src/services/doctor.service.ts @@ -0,0 +1,64 @@ +import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { Service } from "typedi"; +import { HttpException } from "@/exceptions/HttpException"; +import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; +import { PrismaClient, Role } from "@prisma/client"; +import { hash } from "bcrypt"; + +// TO BE CHANGED +const prisma = new PrismaClient(); + +@Service() +export class DoctorService { + + public async signup(doctorData: DoctorSignupRequestDto): Promise { + // Check if email already exists + const existingUser = await prisma.user.findUnique({ + where: { email: doctorData.email } + }); + + if (existingUser) { + const error = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // Generate username from email + const username = doctorData.email.split('@')[0]; + + // Check if username exists + const existingUsername = await prisma.user.findUnique({ + where: { username } + }); + + if (existingUsername) { + const error = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const hashedPassword = await hash(doctorData.password, 10); + + // Create user with doctor role + const createdUser = await prisma.user.create({ + data: { + email: doctorData.email, + name: doctorData.name, + username, + phone: doctorData.phone, + gender: doctorData.gender, + date_of_birth: doctorData.date_of_birth, + password_hash: hashedPassword, + role: Role.DOCTOR, + isVerified: false, + hasCompletedProfile: true, + }, + }); + + await prisma.doctor.create({ + data: { + id: createdUser.id, + specialization: doctorData.specialization, + }, + }); + } + +} From f0e9a88aaa17e455fc47174601c40de4554da993 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 12:31:58 +0200 Subject: [PATCH 052/210] synced migrations with dev and added missing swagger annotations --- .../migration.sql | 32 ++ .../migration.sql | 14 + .../migration.sql | 32 ++ src/prisma/schema.prisma | 51 ++- src/routes/admin.route.ts | 59 +++- src/routes/auth.route.ts | 14 +- src/routes/doctors.route.ts | 23 ++ src/routes/superAdmin.route.ts | 12 +- src/server.ts | 3 +- src/swagger-output.json | 295 ++++++++++++++++-- src/swagger.js | 3 +- 11 files changed, 473 insertions(+), 65 deletions(-) create mode 100644 src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql create mode 100644 src/prisma/migrations/20251213100731_merging_with_latest_dev/migration.sql create mode 100644 src/prisma/migrations/20251213101242_readding_the_medical_record_table/migration.sql diff --git a/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql b/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql new file mode 100644 index 0000000..4cb91f7 --- /dev/null +++ b/src/prisma/migrations/20251111114052_add_medical_records_table/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "RecordType" AS ENUM ('LAB_RESULT', 'SCAN', 'DIAGNOSIS', 'VISIT_SUMMARY'); + +-- CreateTable +CREATE TABLE "MedicalRecords" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT, + "name" VARCHAR(255) NOT NULL, + "cid" VARCHAR(255) NOT NULL, + "type" "RecordType" NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "MedicalRecords_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "MedicalRecords_cid_key" ON "MedicalRecords"("cid"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_patient_id_idx" ON "MedicalRecords"("patient_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_doctor_id_idx" ON "MedicalRecords"("doctor_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_cid_idx" ON "MedicalRecords"("cid"); + +-- AddForeignKey +ALTER TABLE "MedicalRecords" ADD CONSTRAINT "MedicalRecords_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/migrations/20251213100731_merging_with_latest_dev/migration.sql b/src/prisma/migrations/20251213100731_merging_with_latest_dev/migration.sql new file mode 100644 index 0000000..79f2843 --- /dev/null +++ b/src/prisma/migrations/20251213100731_merging_with_latest_dev/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - You are about to drop the `MedicalRecords` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "public"."MedicalRecords" DROP CONSTRAINT "MedicalRecords_patient_id_fkey"; + +-- DropTable +DROP TABLE "public"."MedicalRecords"; + +-- DropEnum +DROP TYPE "public"."RecordType"; diff --git a/src/prisma/migrations/20251213101242_readding_the_medical_record_table/migration.sql b/src/prisma/migrations/20251213101242_readding_the_medical_record_table/migration.sql new file mode 100644 index 0000000..4cb91f7 --- /dev/null +++ b/src/prisma/migrations/20251213101242_readding_the_medical_record_table/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "RecordType" AS ENUM ('LAB_RESULT', 'SCAN', 'DIAGNOSIS', 'VISIT_SUMMARY'); + +-- CreateTable +CREATE TABLE "MedicalRecords" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "doctor_id" TEXT, + "name" VARCHAR(255) NOT NULL, + "cid" VARCHAR(255) NOT NULL, + "type" "RecordType" NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "MedicalRecords_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "MedicalRecords_cid_key" ON "MedicalRecords"("cid"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_patient_id_idx" ON "MedicalRecords"("patient_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_doctor_id_idx" ON "MedicalRecords"("doctor_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_cid_idx" ON "MedicalRecords"("cid"); + +-- AddForeignKey +ALTER TABLE "MedicalRecords" ADD CONSTRAINT "MedicalRecords_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index d493357..d6f1b95 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -33,18 +33,19 @@ model User { deleted_at DateTime? // Relations - patient Patient? @relation("UserAsPatient") - doctor Doctor? @relation("UserAsDoctor") - appointments_as_patient Appointment[] @relation("PatientAppointments") - appointments_as_doctor Appointment[] @relation("DoctorAppointments") - medications_as_patient Medication[] @relation("PatientMedications") - medications_as_doctor Medication[] @relation("DoctorMedications") - scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") - scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") - clinics_as_nurse ClinicNurse[] @relation("NurseClinics") - audit_logs AuditLog[] @relation("UserAuditLogs") - controlled_patients Patient[] @relation("ControllingNurse") - refresh_tokens RefreshToken[] @relation("UserRefreshTokens") + patient Patient? @relation("UserAsPatient") + doctor Doctor? @relation("UserAsDoctor") + appointments_as_patient Appointment[] @relation("PatientAppointments") + appointments_as_doctor Appointment[] @relation("DoctorAppointments") + medications_as_patient Medication[] @relation("PatientMedications") + medications_as_doctor Medication[] @relation("DoctorMedications") + scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") + clinics_as_nurse ClinicNurse[] @relation("NurseClinics") + audit_logs AuditLog[] @relation("UserAuditLogs") + controlled_patients Patient[] @relation("ControllingNurse") + refresh_tokens RefreshToken[] @relation("UserRefreshTokens") + medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") @@map("Users") } @@ -207,6 +208,25 @@ model AuditLog { @@map("AuditLogs") } +model MedicalRecord { + id String @id @default(uuid()) + patient_id String + doctor_id String? + name String @db.VarChar(255) + cid String @unique @db.VarChar(255) + type RecordType + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id], onDelete: Restrict) + + @@index([patient_id]) + @@index([doctor_id]) + @@index([cid]) + @@map("MedicalRecords") +} + model RefreshToken { id String @id @default(uuid()) user_id String @@ -258,3 +278,10 @@ enum Role { NURSE PATIENT } + +enum RecordType { + LAB_RESULT + SCAN + DIAGNOSIS + VISIT_SUMMARY +} diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index d71828f..e87d79b 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -35,7 +35,7 @@ export class AdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -68,7 +68,7 @@ export class AdminRoute implements Routes { #swagger.tags = ['Admin'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -96,7 +96,30 @@ export class AdminRoute implements Routes { this.router.get( '/admin/doctors/unverified', - /* #swagger.tags = ['Admin'] */ + /* + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Unverified doctors retrieved successfully', + schema: { + data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', + username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, date_of_birth: '1990-01-01', photoUrl: null, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null } }], + message: 'Unverified doctors retrieved successfully' + } + } + */ AuthMiddleware, RoleMiddleware(Role.ADMIN), LanguageMiddleware, @@ -105,7 +128,35 @@ export class AdminRoute implements Routes { this.router.patch( '/admin/doctors/verify/:id', - /* #swagger.tags = ['Admin'] */ + /* + #swagger.tags = ['Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Verification status', + required: true, + schema: { + $isVerified: true + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Doctor verification status updated successfully', + schema: { + message: 'Doctor verification status updated successfully' + } + } + */ AuthMiddleware, RoleMiddleware(Role.ADMIN), this.adminController.updateDoctorVerificationStatus, diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index e11bc33..b13e724 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -89,7 +89,7 @@ export class AuthRoute implements Routes { #swagger.tags = ['Auth'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -108,7 +108,7 @@ export class AuthRoute implements Routes { #swagger.tags = ['Auth'] #swagger.parameters['RefreshToken'] = { in: 'header', - description: 'Refresh token (sent via RefreshToken cookie or Authorization header)', + description: 'Refresh token (sent via RefreshToken cookie)', required: false, type: 'string' } @@ -139,7 +139,7 @@ export class AuthRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -168,7 +168,7 @@ export class AuthRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie or)', required: false, type: 'string' } @@ -230,7 +230,7 @@ export class AuthRoute implements Routes { #swagger.tags = ['Auth'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cooki)', required: false, type: 'string' } @@ -277,7 +277,7 @@ export class AuthRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie or)', required: false, type: 'string' } @@ -300,7 +300,7 @@ export class AuthRoute implements Routes { #swagger.tags = ['Auth'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index dd631d6..fc4ced5 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -16,6 +16,29 @@ export class DoctorsRoute implements Routes { private initializeRoutes() { this.router.post( `/doctors/signup`, + /* + #swagger.tags = ['Doctors'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Doctor signup data', + required: true, + schema: { + $email: 'doctor@example.com', + $name: 'Dr. Smith', + $phone: '1234567890', + $password: 'SecurePassword123', + $gender: 'MALE or FEMALE', + date_of_birth: '1990-01-01', + $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' + } + } + #swagger.responses[201] = { + description: 'Doctor signup successful', + schema: { + message: 'Doctor registered successfully' + } + } + */ ValidationMiddleware(DoctorSignupRequestDto), this.doctorsController.doctorSignup ); diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 1536949..b269e36 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -41,7 +41,7 @@ export class SuperAdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -76,7 +76,7 @@ export class SuperAdminRoute implements Routes { #swagger.tags = ['Super Admin'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -117,7 +117,7 @@ export class SuperAdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -164,7 +164,7 @@ export class SuperAdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -197,7 +197,7 @@ export class SuperAdminRoute implements Routes { #swagger.tags = ['Super Admin'] #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -235,7 +235,7 @@ export class SuperAdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } diff --git a/src/server.ts b/src/server.ts index f799f33..28aa85b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,8 +4,9 @@ import { AdminRoute } from '@routes/admin.route'; import { ValidateEnv } from '@utils/validateEnv'; import { FabricRoute } from '@routes/fabric.route'; import { SuperAdminRoute } from './routes/superAdmin.route'; +import { DoctorsRoute } from './routes/doctors.route'; ValidateEnv(); -const app = new App([new AuthRoute(), new FabricRoute(), new AdminRoute() , new SuperAdminRoute()]); +const app = new App([new AuthRoute(), new FabricRoute(), new AdminRoute() , new SuperAdminRoute(), new DoctorsRoute()]); app.listen(); diff --git a/src/swagger-output.json b/src/swagger-output.json index 0e99589..575674e 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -206,7 +206,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -240,7 +240,7 @@ { "name": "RefreshToken", "in": "header", - "description": "Refresh token (sent via RefreshToken cookie or Authorization header)", + "description": "Refresh token (sent via RefreshToken cookie)", "required": false, "type": "string" } @@ -319,7 +319,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -384,7 +384,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie or)", "required": false, "type": "string" } @@ -517,7 +517,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cooki)", "required": false, "type": "string" } @@ -595,7 +595,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie or)", "required": false, "type": "string" } @@ -638,7 +638,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -824,7 +824,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -920,7 +920,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -943,6 +943,10 @@ "items": { "type": "object", "properties": { + "id": { + "type": "string", + "example": "1" + }, "email": { "type": "string", "example": "doctor@example.com" @@ -1011,6 +1015,166 @@ } } }, + "/admin/doctors/unverified": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Unverified doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "photoUrl": {}, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + } + } + } + }, + "message": { + "type": "string", + "example": "Unverified doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/doctors/verify/{id}": { + "patch": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "body", + "in": "body", + "description": "Verification status", + "required": true, + "schema": { + "type": "object", + "properties": { + "isVerified": { + "type": "boolean", + "example": true + } + }, + "required": [ + "isVerified" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor verification status updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Doctor verification status updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/admin/doctors/{id}": { "get": { "tags": [ @@ -1114,25 +1278,6 @@ } } } - }, - "patch": { - "tags": [ - "Admin" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "responses": { - "default": { - "description": "" - } - } } }, "/super-admin/admins": { @@ -1188,7 +1333,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -1262,7 +1407,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -1278,6 +1423,10 @@ "items": { "type": "object", "properties": { + "id": { + "type": "string", + "example": "1" + }, "email": { "type": "string", "example": "admin@example.com" @@ -1348,7 +1497,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" } @@ -1462,7 +1611,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -1558,7 +1707,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -1581,6 +1730,10 @@ "items": { "type": "object", "properties": { + "id": { + "type": "string", + "example": "1" + }, "email": { "type": "string", "example": "doctor@example.com" @@ -1666,7 +1819,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -1753,6 +1906,80 @@ } } } + }, + "/doctors/signup": { + "post": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor signup data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "password": { + "type": "string", + "example": "SecurePassword123" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "password", + "gender", + "specialization" + ] + } + } + ], + "responses": { + "201": { + "description": "Doctor signup successful", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Doctor registered successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index cd26d75..84e24d0 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -17,6 +17,7 @@ const doc = { }; const outputFile = './swagger-output.json'; -const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/admin.route.ts', './src/routes/superAdmin.route.ts']; +const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/admin.route.ts', + './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From 00b27aa9a2486a892f12dd93644eee2303a40588 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 15:45:17 +0200 Subject: [PATCH 053/210] added doctorAccountStatus Enum --- src/dtos/admins.dto.ts | 3 +- src/interfaces/users.interface.ts | 1 + .../migration.sql | 5 +++ src/prisma/schema.prisma | 15 +++++--- src/routes/admin.route.ts | 12 +++---- src/routes/auth.route.ts | 2 +- src/services/admin.service.ts | 26 +++++++++----- src/services/auth.service.ts | 3 +- src/services/doctor.service.ts | 3 +- src/swagger-output.json | 34 +++++++++++++++---- 10 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 src/prisma/migrations/20251213134456_added_doctor_account_status_enum/migration.sql diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 50941dc..3047c5d 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -1,4 +1,4 @@ -import { Gender, Role } from "@prisma/client"; +import { DoctorAccountStatus, Gender, Role } from "@prisma/client"; import { IsEmail, IsNotEmpty, IsString } from "class-validator"; import { IsValidSpecialization } from "@/validators/specialization.validator"; import { TransformSpecialization } from "@/utils/specializationTransform"; @@ -42,5 +42,6 @@ export class DoctorFromAdminResponseDto { public doctor?: { specialization: string; avg_time?: Date; + account_status?: DoctorAccountStatus; }; } \ No newline at end of file diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 2c3a978..d9673e1 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -60,6 +60,7 @@ export interface UserLoginData { phone: string, gender: Gender, date_of_birth: Date, + role: Role, isVerified: Boolean, hasCompletedProfile: Boolean, } diff --git a/src/prisma/migrations/20251213134456_added_doctor_account_status_enum/migration.sql b/src/prisma/migrations/20251213134456_added_doctor_account_status_enum/migration.sql new file mode 100644 index 0000000..ddede4c --- /dev/null +++ b/src/prisma/migrations/20251213134456_added_doctor_account_status_enum/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "DoctorAccountStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); + +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "account_status" "DoctorAccountStatus" NOT NULL DEFAULT 'PENDING'; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index d6f1b95..f984d9f 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -51,11 +51,12 @@ model User { } model Doctor { - id String @id @default(uuid()) - specialization String @db.VarChar(255) - avg_time DateTime? @db.Time(0) + id String @id @default(uuid()) + specialization String @db.VarChar(255) + avg_time DateTime? @db.Time(0) + account_status DoctorAccountStatus @default(PENDING) // Relations - user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) + user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) clinic_doctors ClinicDoctor[] @@map("Doctor") @@ -285,3 +286,9 @@ enum RecordType { DIAGNOSIS VISIT_SUMMARY } + +enum DoctorAccountStatus { + PENDING + APPROVED + REJECTED +} diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index e87d79b..a781730 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -50,7 +50,7 @@ export class AdminRoute implements Routes { schema: { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status: 'PENDING' }, photoUrl: null }, message: 'Doctor added successfully' } } @@ -83,7 +83,7 @@ export class AdminRoute implements Routes { schema: { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' }, photoUrl: null }], message: 'Doctors retrieved successfully' } } @@ -115,7 +115,7 @@ export class AdminRoute implements Routes { schema: { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, date_of_birth: '1990-01-01', photoUrl: null, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null } }], + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' } }], message: 'Unverified doctors retrieved successfully' } } @@ -141,7 +141,7 @@ export class AdminRoute implements Routes { description: 'Verification status', required: true, schema: { - $isVerified: true + $isApproved: true } } #swagger.parameters['Authorization'] = { @@ -174,7 +174,7 @@ export class AdminRoute implements Routes { } #swagger.parameters['Authorization'] = { in: 'header', - description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + description: 'Bearer access token (sent via Authorization cookie)', required: false, type: 'string' } @@ -189,7 +189,7 @@ export class AdminRoute implements Routes { schema: { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status: 'APPROVED' }, photoUrl: null }, message: 'Doctor retrieved successfully' } } diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index b13e724..7367803 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -74,7 +74,7 @@ export class AuthRoute implements Routes { #swagger.responses[200] = { description: 'Login successful', schema: { - data: { id: 1, email: 'user@example.com', name: 'John Doe' }, + data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT'}, message: 'Logged In Successfully' } } diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index e159813..c4fce5c 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -1,4 +1,4 @@ -import { PrismaClient, Role } from '@prisma/client'; +import { DoctorAccountStatus, PrismaClient, Role } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto } from '@/dtos/admins.dto'; @@ -60,6 +60,7 @@ export class AdminService { data: { id: createdUser.id, specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") + account_status: DoctorAccountStatus.APPROVED, } }); const createdDoctor = await prisma.user.findUnique({ @@ -83,7 +84,7 @@ export class AdminService { }, } }); - + return createdDoctor; } @@ -107,7 +108,8 @@ export class AdminService { doctor: { select: { specialization: true, - avg_time: true + avg_time: true, + account_status: true } }, } @@ -136,7 +138,8 @@ export class AdminService { doctor: { select: { specialization: true, - avg_time: true + avg_time: true, + account_status: true } }, } @@ -153,7 +156,7 @@ export class AdminService { public async getUnverifiedDoctors(): Promise { const unverifiedDoctors = await prisma.user.findMany({ - where: { role: Role.DOCTOR, isVerified: false }, + where: { role: Role.DOCTOR, doctor: { account_status: DoctorAccountStatus.PENDING } }, select: { id: true, name: true, @@ -167,7 +170,8 @@ export class AdminService { doctor: { select: { specialization: true, - avg_time: true + avg_time: true, + account_status: true } }, }, @@ -175,7 +179,7 @@ export class AdminService { return unverifiedDoctors } - public async updateDoctorVerificationStatus(doctorId: string, isVerified: boolean): Promise { + public async updateDoctorVerificationStatus(doctorId: string, isApproved: boolean): Promise { const doctor = await prisma.user.findUnique({ where: { id: doctorId, role: Role.DOCTOR }, @@ -186,7 +190,13 @@ export class AdminService { } await prisma.user.update({ where: { id: doctorId }, - data: { isVerified }, + data: { + doctor: { + update: { + account_status: isApproved ? DoctorAccountStatus.APPROVED : DoctorAccountStatus.REJECTED, + } + } + } }); } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 9c93f59..42d5487 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -77,7 +77,7 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - const { name, gender, date_of_birth, email, isVerified, username, phone, hasCompletedProfile } = findUser; + const { name, gender, date_of_birth, email, isVerified, username, phone,role, hasCompletedProfile } = findUser; const patientLoginData: UserLoginData = { name, email, @@ -85,6 +85,7 @@ export class AuthService { phone, gender, date_of_birth, + role, isVerified, hasCompletedProfile }; diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 42af9ee..2e386b7 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -2,7 +2,7 @@ import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; -import { PrismaClient, Role } from "@prisma/client"; +import { DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash } from "bcrypt"; // TO BE CHANGED @@ -57,6 +57,7 @@ export class DoctorService { data: { id: createdUser.id, specialization: doctorData.specialization, + account_status: DoctorAccountStatus.PENDING, }, }); } diff --git a/src/swagger-output.json b/src/swagger-output.json index 575674e..da0e9a0 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -180,6 +180,10 @@ "name": { "type": "string", "example": "John Doe" + }, + "role": { + "type": "string", + "example": "PATIENT" } } }, @@ -893,7 +897,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "PENDING" + } } }, "photoUrl": {} @@ -995,7 +1003,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } } }, "photoUrl": {} @@ -1097,7 +1109,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } } } } @@ -1138,13 +1154,13 @@ "schema": { "type": "object", "properties": { - "isVerified": { + "isApproved": { "type": "boolean", "example": true } }, "required": [ - "isVerified" + "isApproved" ] } }, @@ -1192,7 +1208,7 @@ { "name": "Authorization", "in": "header", - "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "description": "Bearer access token (sent via Authorization cookie)", "required": false, "type": "string" }, @@ -1261,7 +1277,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } } }, "photoUrl": {} From ddc6442db50302ae75e4e3d05cbee06c8171d339 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 16:19:48 +0200 Subject: [PATCH 054/210] added account status upon doctor login --- src/interfaces/users.interface.ts | 7 ++++++- src/routes/auth.route.ts | 2 +- src/services/auth.service.ts | 8 ++++++-- src/swagger-output.json | 13 +++++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index d9673e1..ce02576 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -3,7 +3,7 @@ import { Medication } from './medications.interface'; import { ScanLab } from './scans-labs.interface'; import { ClinicNurse, ClinicDoctor } from './clinics.interface'; import { AuditLog } from './audit-logs.interface'; -import { Gender, Role } from '@prisma/client'; +import { DoctorAccountStatus, Gender, Role } from '@prisma/client'; export interface User { id: string; @@ -48,6 +48,7 @@ export interface Doctor { id: string; specialization: string; avg_time?: Date; + account_status: DoctorAccountStatus; user: User; clinic_doctors?: ClinicDoctor[]; @@ -63,4 +64,8 @@ export interface UserLoginData { role: Role, isVerified: Boolean, hasCompletedProfile: Boolean, + doctor?: { + specialization: string; + account_status: DoctorAccountStatus; + } } diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 7367803..6a1d807 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -74,7 +74,7 @@ export class AuthRoute implements Routes { #swagger.responses[200] = { description: 'Login successful', schema: { - data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT'}, + data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT' , doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, message: 'Logged In Successfully' } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 42d5487..cde6f64 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -77,7 +77,7 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - const { name, gender, date_of_birth, email, isVerified, username, phone,role, hasCompletedProfile } = findUser; + const { name, gender, date_of_birth, email, isVerified, username, phone, role, hasCompletedProfile, doctor } = findUser; const patientLoginData: UserLoginData = { name, email, @@ -87,7 +87,11 @@ export class AuthService { date_of_birth, role, isVerified, - hasCompletedProfile + hasCompletedProfile, + doctor: doctor ? { + specialization: doctor.specialization, + account_status: doctor.account_status + } : undefined }; const tokenResponse = await this.createTokens(findUser, userData.rememberMe); diff --git a/src/swagger-output.json b/src/swagger-output.json index da0e9a0..b87a62b 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -184,6 +184,19 @@ "role": { "type": "string", "example": "PATIENT" + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "string", + "example": "Cardiology" + }, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } } } }, From ccd91a236a84c5cd4f72b44350b84af36f9adc88 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 18:57:06 +0200 Subject: [PATCH 055/210] swagger edits --- src/routes/superAdmin.route.ts | 4 ++-- src/swagger-output.json | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index b269e36..015d901 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -212,7 +212,7 @@ export class SuperAdminRoute implements Routes { schema: { data: [{ id: '1', email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }], + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }], message: 'Doctors retrieved successfully' } } @@ -250,7 +250,7 @@ export class SuperAdminRoute implements Routes { schema: { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, + doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }, message: 'Doctor retrieved successfully' } } diff --git a/src/swagger-output.json b/src/swagger-output.json index b87a62b..3725ba1 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1815,7 +1815,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "Approved" + } } }, "photoUrl": {} @@ -1921,7 +1925,11 @@ } } }, - "avg_time": {} + "avg_time": {}, + "account_status": { + "type": "string", + "example": "Approved" + } } }, "photoUrl": {} From 6f81a5575c66173a9f7e32bdeebd20e5437c0257 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 19:10:25 +0200 Subject: [PATCH 056/210] added date of birth to doctor add by admin --- src/dtos/admins.dto.ts | 3 +++ src/routes/admin.route.ts | 1 + src/routes/superAdmin.route.ts | 1 + src/services/admin.service.ts | 2 +- src/swagger-output.json | 10 ++++++++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 3047c5d..4c6eca0 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -16,6 +16,9 @@ export class AddDoctorFromAdminDto { @IsNotEmpty() public phone: string; + @IsNotEmpty() + public date_of_birth: Date; + @IsString() public gender: Gender; diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index a781730..4c6a54a 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -30,6 +30,7 @@ export class AdminRoute implements Routes { $name: 'Dr. Smith', $phone: '1234567890', $gender: 'MALE or FEMALE', + $date_of_birth: '1990-01-01', $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' } } diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 015d901..b606f70 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -159,6 +159,7 @@ export class SuperAdminRoute implements Routes { $name: 'Dr. John Doe', $phone: '1234567890', $gender: 'MALE or FEMALE', + $date_of_birth: '1990-01-01', $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' } } diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index c4fce5c..394e40d 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -47,7 +47,7 @@ export class AdminService { username, phone: doctorData.phone, gender: doctorData.gender, - date_of_birth: new Date('1990-01-01'), + date_of_birth: doctorData.date_of_birth, password_hash: hashedPassword, role: Role.DOCTOR, isVerified: true, diff --git a/src/swagger-output.json b/src/swagger-output.json index 3725ba1..4f47d65 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -824,6 +824,10 @@ "type": "string", "example": "MALE or FEMALE" }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, "specialization": { "type": "string", "example": "CARDIOLOGY or امراض القلب or Cardiology" @@ -834,6 +838,7 @@ "name", "phone", "gender", + "date_of_birth", "specialization" ] } @@ -1627,6 +1632,10 @@ "type": "string", "example": "MALE or FEMALE" }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, "specialization": { "type": "string", "example": "CARDIOLOGY or امراض القلب or Cardiology" @@ -1637,6 +1646,7 @@ "name", "phone", "gender", + "date_of_birth", "specialization" ] } From 972759229181d863b534817c32ee25f1b01b56d2 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 13 Dec 2025 20:00:57 +0200 Subject: [PATCH 057/210] fixed date_of_birth error --- src/services/admin.service.ts | 2 +- src/services/doctor.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 394e40d..50dd5d0 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -47,7 +47,7 @@ export class AdminService { username, phone: doctorData.phone, gender: doctorData.gender, - date_of_birth: doctorData.date_of_birth, + date_of_birth: new Date(doctorData.date_of_birth), password_hash: hashedPassword, role: Role.DOCTOR, isVerified: true, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 2e386b7..71d8525 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -45,7 +45,7 @@ export class DoctorService { username, phone: doctorData.phone, gender: doctorData.gender, - date_of_birth: doctorData.date_of_birth, + date_of_birth: new Date(doctorData.date_of_birth), password_hash: hashedPassword, role: Role.DOCTOR, isVerified: false, From a620069f3555d042bb032090e7163b48d12d0e30 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 22 Jan 2026 14:40:34 +0200 Subject: [PATCH 058/210] Doctor Login Endpoint --- package.json | 1 + src/controllers/doctor.controller.ts | 18 ++++- src/dtos/doctors.dto.ts | 12 ++++ src/interfaces/doctors.interface.ts | 14 ++++ src/interfaces/users.interface.ts | 2 +- src/routes/doctors.route.ts | 42 ++++++++++- src/services/auth.service.ts | 6 +- src/services/doctor.service.ts | 77 +++++++++++++++++++- src/services/googleAuth.service.ts | 1 + src/swagger-output.json | 101 +++++++++++++++++++++++++++ src/swagger.js | 1 + src/utils/errorMessages.ts | 6 ++ src/utils/errorWrapper.ts | 12 ++++ 13 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/interfaces/doctors.interface.ts create mode 100644 src/utils/errorWrapper.ts diff --git a/package.json b/package.json index b1d3ca4..5ff7bf4 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "prisma:migrate": "dotenv -e .env.development.local -- npx prisma migrate dev --schema=src/prisma/schema.prisma", "prisma:generate": "prisma generate", "prisma:studio": "dotenv -e .env.development.local -- prisma studio", + "swagger:generate": "node ./src/swagger.js", "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", "deploy:dev": "pm2 start ecosystem.config.js --only dev" }, diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 60f4eb8..e304a5b 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,5 +1,5 @@ -import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { RequestWithLanguage } from "@/middlewares/language.middleware"; import { DoctorService } from "@/services/doctor.service"; import { NextFunction, Request, Response } from "express"; @@ -9,9 +9,23 @@ import { Container } from "typedi"; export class DoctorController { public doctorService = Container.get(DoctorService); - public doctorSignup = async (req: RequestWithLanguage, res: Response, next: NextFunction) => { + public doctorSignup = async (req: Request, res: Response, next: NextFunction) => { const doctorData: DoctorSignupRequestDto = req.body; await this.doctorService.signup(doctorData); res.status(201).json({ message: 'Doctor signed up successfully' }); }; + + public doctorLogin = async (req: Request, res: Response, next: NextFunction) => { + const doctorLoginData: DoctorLoginRequestDto = req.body; + const loginResult = await this.doctorService.login(doctorLoginData); + + if (loginResult === false) { + // For testing purposes only - To Be CHANGED according to Frontend Link + res.redirect('/test') + } else if (typeof loginResult === 'object') { + const { cookies, doctorAccountData } = loginResult; + res.setHeader('Set-Cookie', cookies); + res.status(200).json({ data: doctorAccountData, message: 'Doctor logged in successfully' }); + } + } } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index f4f319c..958cfb7 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -32,4 +32,16 @@ export class DoctorSignupRequestDto { message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' }) public specialization: string; +} + +export class DoctorLoginRequestDto { + @IsNotEmpty() + public emailOrUsername: string; + + @IsString() + @IsNotEmpty() + public password: string; + + @IsString() + public rememberMe?: boolean; } \ No newline at end of file diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts new file mode 100644 index 0000000..5cdcf6d --- /dev/null +++ b/src/interfaces/doctors.interface.ts @@ -0,0 +1,14 @@ +import { DoctorAccountStatus } from "@prisma/client"; + +export interface DoctorLoginData { + id: string, + name: string, + email: string, + username: string, + phone: string, + gender: string, + doctor: { + specialization: string, + account_status: DoctorAccountStatus + } +} \ No newline at end of file diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index ce02576..3fb0ede 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -22,7 +22,7 @@ export interface User { deleted_at?: Date; patient?: Patient; - doctor?: Doctor; + doctor?: Partial; appointments_as_patient?: Appointment[]; appointments_as_doctor?: Appointment[]; medications_as_patient?: Medication[]; diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index fc4ced5..76ec995 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,8 +1,9 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; +import { errorWrapper } from "@/utils/errorWrapper"; export class DoctorsRoute implements Routes { @@ -40,7 +41,44 @@ export class DoctorsRoute implements Routes { } */ ValidationMiddleware(DoctorSignupRequestDto), - this.doctorsController.doctorSignup + errorWrapper(this.doctorsController.doctorSignup) + ); + + this.router.post( + `/doctors/login`, + /* + #swagger.tags = ['Doctors'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Doctor login data', + required: true, + schema: { + $emailOrUsername: 'doctor@example.com', + $password: 'SecurePassword123', + $rememberMe: "true" + } + } + #swagger.responses[200] = { + description: 'Doctor login successful', + schema: { + data: { + id: 1, + email: 'test@example.com', + name: 'Dr. Smith', + username: 'drsmith', + phone: '1234567890', + gender: 'MALE', + doctor: { + specialization: 'CARDIOLOGY', + account_status: 'APPROVED' + } + }, + message: 'Doctor logged in successfully' + } + } + */ + ValidationMiddleware(DoctorLoginRequestDto), + errorWrapper(this.doctorsController.doctorLogin) ); } } \ No newline at end of file diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index cde6f64..d2a1a09 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -136,7 +136,7 @@ export class AuthService { } - public async createTokens(user: User, rememberMe: boolean = false): Promise { + public async createTokens(user: Partial, rememberMe: boolean = false): Promise { const accessToken = this.createAccessToken(user); if (rememberMe) { @@ -147,7 +147,7 @@ export class AuthService { return { accessToken }; } - public createAccessToken(user: User): AccessTokenData { + public createAccessToken(user: Partial): AccessTokenData { const dataStoredInToken: DataStoredInToken = { id: user.id }; const secretKey: string = SECRET_KEY; const expiresIn: number = this.parseTimeToSeconds(ACCESS_TOKEN_EXPIRY); @@ -155,7 +155,7 @@ export class AuthService { return { expiresIn, token: sign(dataStoredInToken, secretKey, { expiresIn }) }; } - public async createRefreshToken(user: User): Promise { + public async createRefreshToken(user: Partial): Promise { const dataStoredInToken: DataStoredInToken = { id: user.id }; const secretKey: string = REFRESH_TOKEN_SECRET; const expiresIn: number = this.parseTimeToSeconds(REFRESH_TOKEN_EXPIRY); diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 71d8525..8bb2e30 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -1,12 +1,15 @@ -import { DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; -import { hash } from "bcrypt"; +import { hash, compare } from "bcrypt"; +import { DoctorLoginData } from "@/interfaces/doctors.interface"; +import { AuthService } from "./auth.service"; // TO BE CHANGED const prisma = new PrismaClient(); +const authService = new AuthService(); @Service() export class DoctorService { @@ -62,4 +65,74 @@ export class DoctorService { }); } + + public async login(doctorLoginData: DoctorLoginRequestDto): Promise<{ cookies: string[]; doctorAccountData: DoctorLoginData } | boolean> { + + // Find user by email or username + const doctorUserData = await prisma.user.findFirst({ + where: { + OR: [ + { email: doctorLoginData.emailOrUsername }, + { username: doctorLoginData.emailOrUsername } + ] + }, + select: { + id: true, + email: true, + username: true, + name: true, + phone: true, + gender: true, + hasCompletedProfile: true, + password_hash: true, + doctor: { + select: { + specialization: true, + account_status: true + } + } + } + }); + + // Check if user exists and password matches + if (!doctorUserData) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const isPasswordMatching = await compare(doctorLoginData.password, doctorUserData.password_hash); + + if (!isPasswordMatching) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (doctorUserData.doctor?.account_status !== DoctorAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!doctorUserData.hasCompletedProfile) { + return false; + } + + const doctorAccountData: DoctorLoginData = + { + id: doctorUserData.id, + name: doctorUserData.name, + email: doctorUserData.email, + username: doctorUserData.username, + phone: doctorUserData.phone, + gender: doctorUserData.gender, + doctor: { + specialization: doctorUserData.doctor?.specialization, + account_status: doctorUserData.doctor?.account_status + } + } + + const token = await authService.createTokens(doctorUserData, doctorLoginData.rememberMe); + const cookies = authService.createCookies(token); + + return { cookies, doctorAccountData }; + } } diff --git a/src/services/googleAuth.service.ts b/src/services/googleAuth.service.ts index b5b13cb..c70710e 100644 --- a/src/services/googleAuth.service.ts +++ b/src/services/googleAuth.service.ts @@ -62,6 +62,7 @@ export class GoogleAuthService { name: true, username: true, phone: true, + role: true, gender: true, date_of_birth: true, isVerified: true, diff --git a/src/swagger-output.json b/src/swagger-output.json index 4f47d65..1f354bc 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -23,6 +23,10 @@ { "name": "MedicalRecords", "description": "Hyperledger Fabric medical record endpoints" + }, + { + "name": "Doctors", + "description": "Doctor account endpoints" } ], "schemes": [ @@ -2031,6 +2035,103 @@ } } } + }, + "/doctors/login": { + "post": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "doctor@example.com" + }, + "password": { + "type": "string", + "example": "SecurePassword123" + }, + "rememberMe": { + "type": "string", + "example": "true" + } + }, + "required": [ + "emailOrUsername", + "password", + "rememberMe" + ] + } + } + ], + "responses": { + "200": { + "description": "Doctor login successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "test@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "username": { + "type": "string", + "example": "drsmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "string", + "example": "CARDIOLOGY" + }, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + } + } + }, + "message": { + "type": "string", + "example": "Doctor logged in successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index 84e24d0..c62f8da 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -12,6 +12,7 @@ const doc = { { name: 'Admin', description: 'Admin endpoints' }, { name: 'Super Admin', description: 'Super Admin endpoints' }, { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, + { name: 'Doctors', description: 'Doctor account endpoints' }, ], }; diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 88861ba..910b0ae 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -80,6 +80,12 @@ export const ErrorMessages = { ar: 'خطأ في المصادقة عبر Google', }, + //Doctor specific errors + DOCTOR_ACCOUNT_NOT_APPROVED: { + en: 'Doctor account is not approved yet', + ar: 'حساب الطبيب غير مفعل بعد', + }, + // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', diff --git a/src/utils/errorWrapper.ts b/src/utils/errorWrapper.ts new file mode 100644 index 0000000..b81a1c8 --- /dev/null +++ b/src/utils/errorWrapper.ts @@ -0,0 +1,12 @@ +import { NextFunction, Request, Response } from 'express'; + +/** + * Wraps async route handlers to catch errors and pass them to the error middleware + * @param fn - The async function to wrap + * @returns A wrapped function that catches errors + */ +export const errorWrapper = (fn: Function) => { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +}; From c592daa7221f868448c25b53ebebc4a361f45f0f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 22 Jan 2026 14:48:27 +0200 Subject: [PATCH 059/210] Refactored Error Handling in auth controller --- src/controllers/auth.controller.ts | 8 ++++++-- src/utils/errorMessages.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 87e6bd0..1ca7e07 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -4,6 +4,8 @@ import { RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { AuthService } from '@services/auth.service'; import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@/dtos/users.dto'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { HttpException } from '@/exceptions/HttpException'; export class AuthController { public auth = Container.get(AuthService); @@ -89,7 +91,8 @@ export class AuthController { const email = await this.auth.getUserEmail(req) const { otp } = req.body; if (!otp) { - throw new Error('OTP is required'); + const error = createBilingualError(400, ErrorMessages.OTP_REQUIRED); + throw new HttpException(error.status , error.message, error.messageAr); } const isSuccessful = await this.auth.verifyEmailOtp(email, otp); res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); @@ -102,7 +105,8 @@ export class AuthController { try { const email = req.body.email; if (!email) { - throw new Error('Email is required'); + const error = createBilingualError(400, ErrorMessages.EMAIL_REQUIRED); + throw new HttpException(error.status , error.message, error.messageAr); } await this.auth.sendPasswordResetEmail(email); res.status(200).json({ message: 'Password Reset Email Sent Successfully' }); diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 910b0ae..69c308d 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -37,6 +37,11 @@ export const ErrorMessages = { en: 'User email not found', ar: 'البريد الإلكتروني للمستخدم غير موجود', }, + + OTP_REQUIRED: { + en: 'OTP is required', + ar: 'رمز التحقق مطلوب', + }, INVALID_OTP: { en: 'Invalid OTP', ar: 'رمز التحقق غير صالح', @@ -54,6 +59,11 @@ export const ErrorMessages = { ar: 'رمز إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية', }, + EMAIL_REQUIRED: { + en: 'Email is required', + ar: 'البريد الإلكتروني مطلوب', + }, + // Authentication middleware errors WRONG_AUTHENTICATION_TOKEN: { en: 'Wrong authentication token', From 0213cb13e5f5bfaef776df631929e2fa8d6e9ae0 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 22 Jan 2026 15:04:34 +0200 Subject: [PATCH 060/210] added doctor set first time password endpoint --- src/controllers/doctor.controller.ts | 10 ++++++++-- src/dtos/doctors.dto.ts | 6 ++++++ src/routes/doctors.route.ts | 27 ++++++++++++++++++++++++++- src/services/doctor.service.ts | 8 ++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index e304a5b..8d24b41 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,6 +1,5 @@ -import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; -import { RequestWithLanguage } from "@/middlewares/language.middleware"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { DoctorService } from "@/services/doctor.service"; import { NextFunction, Request, Response } from "express"; import { Container } from "typedi"; @@ -28,4 +27,11 @@ export class DoctorController { res.status(200).json({ data: doctorAccountData, message: 'Doctor logged in successfully' }); } } + + public doctorSetPassword = async (req: Request, res: Response, next: NextFunction) => { + const doctorId= req.params.id; + const { password } : DoctorSetPasswordRequestDto = req.body; + await this.doctorService.setPassword(doctorId, password); + res.status(200).json({ message: 'Password set successfully' }); + } } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index 958cfb7..979a3bc 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -44,4 +44,10 @@ export class DoctorLoginRequestDto { @IsString() public rememberMe?: boolean; +} + +export class DoctorSetPasswordRequestDto { + @IsString() + @IsNotEmpty() + public password: string; } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 76ec995..6755d32 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,9 +1,10 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; import { errorWrapper } from "@/utils/errorWrapper"; +import { AuthMiddleware } from "@/middlewares/auth.middleware"; export class DoctorsRoute implements Routes { @@ -80,5 +81,29 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(DoctorLoginRequestDto), errorWrapper(this.doctorsController.doctorLogin) ); + this.router.patch( + `/doctors/:id/set-password`, + /* + #swagger.tags = ['Doctors'] + #swagger.parameters['id'] = { description: 'Doctor ID' } + #swagger.parameters['body'] = { + in: 'body', + description: 'New password data', + required: true, + schema: { + $password: 'NewSecurePassword123' + } + } + #swagger.responses[200] = { + description: 'Password set successfully', + schema: { + message: 'Password updated successfully' + } + } + */ + ValidationMiddleware(DoctorSetPasswordRequestDto), + AuthMiddleware, + errorWrapper(this.doctorsController.doctorSetPassword) + ); } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 8bb2e30..daa9470 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -135,4 +135,12 @@ export class DoctorService { return { cookies, doctorAccountData }; } + + public async setPassword(doctorId: string, password: string): Promise { + const hashedPassword = await hash(password, 10); + await prisma.user.update({ + where: {id: doctorId}, + data: {password_hash: hashedPassword} + }); + } } From 2da66d36969055abb33b336a5bbb10865128d0e2 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 22 Jan 2026 15:09:17 +0200 Subject: [PATCH 061/210] added some checking logic to set first time password for doctor --- src/services/doctor.service.ts | 19 +++++++++++++++++-- src/utils/errorMessages.ts | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index daa9470..22f6e60 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -138,9 +138,24 @@ export class DoctorService { public async setPassword(doctorId: string, password: string): Promise { const hashedPassword = await hash(password, 10); + const doctorUserData = await prisma.user.findUnique({ + where: { id: doctorId }, + select: { hasCompletedProfile: true } + }); + if (!doctorUserData) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + if (doctorUserData.hasCompletedProfile) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_PASSWORD_ALREADY_SET); + throw new HttpException(error.status, error.message, error.messageAr); + } await prisma.user.update({ - where: {id: doctorId}, - data: {password_hash: hashedPassword} + where: { id: doctorId }, + data: { + password_hash: hashedPassword, + hasCompletedProfile: true + } }); } } diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 69c308d..4467cd8 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -95,6 +95,10 @@ export const ErrorMessages = { en: 'Doctor account is not approved yet', ar: 'حساب الطبيب غير مفعل بعد', }, + DOCTOR_PASSWORD_ALREADY_SET: { + en: 'Password has already been set', + ar: 'تم تعيين كلمة المرور بالفعل', + }, // Generic errors SOMETHING_WENT_WRONG: { From 148b20d62ea2dc03ddc92c359117b585a40c5389 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 23 Jan 2026 07:37:24 +0200 Subject: [PATCH 062/210] added profile picture upload endpoint --- .gitignore | 3 +- package-lock.json | 31 +++++++-- package.json | 1 + src/config/index.ts | 5 +- src/controllers/doctor.controller.ts | 28 +++++++- src/dtos/doctors.dto.ts | 4 ++ src/middlewares/multer.middleware.ts | 39 +++++++++++ src/middlewares/validation.middleware.ts | 26 +++++++- src/routes/doctors.route.ts | 29 +++++++-- src/services/doctor.service.ts | 10 +++ src/services/user.service.ts | 37 +++++++++++ src/swagger-output.json | 82 ++++++++++++++++++++++++ src/utils/cloudinary.ts | 11 ++++ src/utils/errorMessages.ts | 9 +++ 14 files changed, 299 insertions(+), 16 deletions(-) create mode 100644 src/middlewares/multer.middleware.ts create mode 100644 src/services/user.service.ts create mode 100644 src/utils/cloudinary.ts diff --git a/.gitignore b/.gitignore index 8526d41..5682e34 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,5 @@ vite.config.ts.timestamp-* # Temporary folders docker-compose-local.yml -docs \ No newline at end of file +docs +uploads \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 92a68af..7988fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", @@ -3582,6 +3583,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -5584,7 +5595,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -8364,7 +8377,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -10349,7 +10364,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -11558,7 +11575,9 @@ } }, "node_modules/systeminformation": { - "version": "5.27.11", + "version": "5.30.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.5.tgz", + "integrity": "sha512-DpWmpCckhwR3hG+6udb6/aQB7PpiqVnvSljrjbKxNSvTRsGsg7NVE3/vouoYf96xgwMxXFKcS4Ux+cnkFwYM7A==", "dev": true, "license": "MIT", "optional": true, @@ -11584,7 +11603,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { diff --git a/package.json b/package.json index 5ff7bf4..8960728 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", diff --git a/src/config/index.ts b/src/config/index.ts index 9390b95..e8cb2d9 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,9 +2,12 @@ import { config } from 'dotenv'; config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` }); export const CREDENTIALS = process.env.CREDENTIALS === 'true'; + export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_TOKEN_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL, GMAIL_USER, GMAIL_APP_PASSWORD, - FRONTEND_URL, SENDER_EMAIL } = process.env; + FRONTEND_URL, SENDER_EMAIL, + CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } = process.env; + export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 8d24b41..d116e84 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,12 +1,17 @@ import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { HttpException } from "@/exceptions/HttpException"; +import { RequestWithUser } from "@/interfaces"; import { DoctorService } from "@/services/doctor.service"; +import { UserService } from "@/services/user.service"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { NextFunction, Request, Response } from "express"; import { Container } from "typedi"; export class DoctorController { public doctorService = Container.get(DoctorService); + public userService = Container.get(UserService); public doctorSignup = async (req: Request, res: Response, next: NextFunction) => { const doctorData: DoctorSignupRequestDto = req.body; @@ -28,10 +33,27 @@ export class DoctorController { } } - public doctorSetPassword = async (req: Request, res: Response, next: NextFunction) => { - const doctorId= req.params.id; - const { password } : DoctorSetPasswordRequestDto = req.body; + public doctorSetPassword = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + const { password }: DoctorSetPasswordRequestDto = req.body; await this.doctorService.setPassword(doctorId, password); res.status(200).json({ message: 'Password set successfully' }); } + + public updateProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + const profilePictureFile = req.file; + + if (!profilePictureFile) { + const error = createBilingualError(400, ErrorMessages.NO_FILE_UPLOADED); + throw new HttpException(error.status, error.message, error.messageAr); + } + const uploadResult = await this.userService.updateProfilePicture(doctorId, profilePictureFile.path); + + await this.doctorService.updateDoctorProfilePicture(doctorId, uploadResult.url, uploadResult.publicId); + + res.status(200).json({ message: 'Profile picture updated successfully'}); + + } + } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index 979a3bc..a0f5f97 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -50,4 +50,8 @@ export class DoctorSetPasswordRequestDto { @IsString() @IsNotEmpty() public password: string; +} + +export class DoctorProfilePictureRequestDto { + profilePicture: Express.Multer.File; } \ No newline at end of file diff --git a/src/middlewares/multer.middleware.ts b/src/middlewares/multer.middleware.ts new file mode 100644 index 0000000..de39cb1 --- /dev/null +++ b/src/middlewares/multer.middleware.ts @@ -0,0 +1,39 @@ +import multer from 'multer'; +import path from 'path'; +import { Request } from 'express'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { HttpException } from '@/exceptions/HttpException'; + +// We use diskStorage so the file is saved to a 'temp' folder first. +const storage = multer.diskStorage({ + destination: (req: Request, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => { + cb(null, 'uploads/'); + }, + filename: (req: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => { + // We create a unique name: "doctor-timestamp.jpg" + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); + } +}); + +// 2. Filter to accept ONLY images +const fileFilter = (req: Request, file: Express.Multer.File, cb: (error: Error | null, acceptFile: boolean) => void) => { + if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { + cb(null, true); // Accept file + } else { + const bilingualError = createBilingualError(400, ErrorMessages.UNSUPPORTED_FILE_FORMAT); + const error = new HttpException(bilingualError.status, bilingualError.message, bilingualError.messageAr); + cb(error, false); // Reject file + } +}; + +// 3. Initialize Multer with limits +const upload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 1024 * 1024 * 3 // Limit file size to 3MB + } +}); + +export default upload; \ No newline at end of file diff --git a/src/middlewares/validation.middleware.ts b/src/middlewares/validation.middleware.ts index c9615c7..8bdf82a 100644 --- a/src/middlewares/validation.middleware.ts +++ b/src/middlewares/validation.middleware.ts @@ -2,17 +2,39 @@ import { plainToInstance } from 'class-transformer'; import { validateOrReject, ValidationError } from 'class-validator'; import { NextFunction, Request, Response } from 'express'; import { HttpException } from '@exceptions/HttpException'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; /** * @name ValidationMiddleware * @description Allows use of decorator and non-decorator based validation - * @param type dto + * @param type dto (pass null to skip DTO validation when only validating file upload) * @param skipMissingProperties When skipping missing properties * @param whitelist Even if your object is an instance of a validation class it can contain additional properties that are not defined * @param forbidNonWhitelisted If you would rather to have an error thrown when any non-whitelisted properties are present + * @param requireFile When true, validates that a file has been uploaded via multer (checks req.file or req.files) */ -export const ValidationMiddleware = (type: any, skipMissingProperties = false, whitelist = false, forbidNonWhitelisted = false) => { +export const ValidationMiddleware = ( + type: any = null, + skipMissingProperties = false, + whitelist = false, + forbidNonWhitelisted = false, + requireFile = false, +) => { return (req: Request, res: Response, next: NextFunction) => { + // Validate file upload if required + if (requireFile) { + const hasFile = req.file || (req.files && (Array.isArray(req.files) ? req.files.length > 0 : Object.keys(req.files).length > 0)); + if (!hasFile) { + const error = createBilingualError(400, ErrorMessages.NO_FILE_UPLOADED) + return next(new HttpException(400, error.message, error.messageAr)); + } + } + + // Skip DTO validation if type is null (file-only validation) + if (type === null) { + return next(); + } + const dto = plainToInstance(type, req.body); validateOrReject(dto, { skipMissingProperties, whitelist, forbidNonWhitelisted }) .then(() => { diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 6755d32..b6203c3 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,10 +1,11 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorProfilePictureRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; import { errorWrapper } from "@/utils/errorWrapper"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import upload from "@/middlewares/multer.middleware"; export class DoctorsRoute implements Routes { @@ -44,7 +45,6 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(DoctorSignupRequestDto), errorWrapper(this.doctorsController.doctorSignup) ); - this.router.post( `/doctors/login`, /* @@ -82,10 +82,9 @@ export class DoctorsRoute implements Routes { errorWrapper(this.doctorsController.doctorLogin) ); this.router.patch( - `/doctors/:id/set-password`, + `/doctors/set-password`, /* #swagger.tags = ['Doctors'] - #swagger.parameters['id'] = { description: 'Doctor ID' } #swagger.parameters['body'] = { in: 'body', description: 'New password data', @@ -105,5 +104,27 @@ export class DoctorsRoute implements Routes { AuthMiddleware, errorWrapper(this.doctorsController.doctorSetPassword) ); + this.router.patch( + `/doctors/profile-picture`, + /* + #swagger.tags = ['Doctors'] + #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['profilePicture'] = { + in: 'formData', + type: 'file', + required: true, + description: 'Profile picture file' + } + #swagger.responses[200] = { + description: 'Profile picture updated successfully', + schema: { + message: 'Profile picture updated successfully', + } + */ + AuthMiddleware, + upload.single('profilePicture'), + ValidationMiddleware(null, false, false, false, true), + errorWrapper(this.doctorsController.updateProfilePicture) + ); } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 22f6e60..431188d 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -158,4 +158,14 @@ export class DoctorService { } }); } + + public async updateDoctorProfilePicture(doctorId: string, photoUrl: string, photoPublicId: string): Promise { + await prisma.user.update({ + where: { id: doctorId }, + data: { + photo_url: photoUrl, + photo_public_id: photoPublicId + } + }); + } } diff --git a/src/services/user.service.ts b/src/services/user.service.ts new file mode 100644 index 0000000..2f4dd73 --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,37 @@ +import cloudinary from "@/utils/cloudinary"; +import { PrismaClient } from "@prisma/client"; +import { Service } from "typedi"; +import fs from "fs"; + +const prisma = new PrismaClient(); + +@Service() +export class UserService { + + public async updateProfilePicture(userId: string, localFilePath: string): Promise<{ url: string; publicId: string }> { + + const oldProfilePictureId = await prisma.user.findUnique({ + where: { id: userId }, + select: { photo_public_id: true } + }); + + if (oldProfilePictureId?.photo_public_id) { + // Delete old profile picture from Cloudinary + await cloudinary.uploader.destroy(oldProfilePictureId.photo_public_id); + } + + // Upload new profile picture to Cloudinary + const uploadResult = await cloudinary.uploader.upload(localFilePath, { + folder: 'doctors/profile_pictures', + overwrite: false, + public_id: `doctor_${userId}_profile_picture_${Date.now()}` + }); + + fs.unlinkSync(localFilePath); // Remove local file after upload + + return { + url: uploadResult.secure_url, + publicId: uploadResult.public_id + } + } +} \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 1f354bc..fda2821 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -2132,6 +2132,88 @@ } } } + }, + "/doctors/set-password": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "New password data", + "required": true, + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "NewSecurePassword123" + } + }, + "required": [ + "password" + ] + } + } + ], + "responses": { + "200": { + "description": "Password set successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/doctors/profile-picture": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "profilePicture", + "in": "formData", + "type": "file", + "required": true, + "description": "Profile picture file" + } + ], + "responses": { + "200": { + "description": "Profile picture updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/utils/cloudinary.ts b/src/utils/cloudinary.ts new file mode 100644 index 0000000..ab7f8c4 --- /dev/null +++ b/src/utils/cloudinary.ts @@ -0,0 +1,11 @@ +import { CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET, CLOUDINARY_CLOUD_NAME } from '@/config'; +import { v2 as cloudinary } from 'cloudinary'; + +// Configure with credentials from your Cloudinary Dashboard +cloudinary.config({ + cloud_name: CLOUDINARY_CLOUD_NAME, + api_key: CLOUDINARY_API_KEY, + api_secret: CLOUDINARY_API_SECRET +}); + +export default cloudinary; \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 4467cd8..3269c7a 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -100,6 +100,15 @@ export const ErrorMessages = { ar: 'تم تعيين كلمة المرور بالفعل', }, + // File upload errors + NO_FILE_UPLOADED: { + en: 'No file uploaded', + ar: 'لم يتم تحميل أي ملف', + }, + UNSUPPORTED_FILE_FORMAT: { + en: 'Unsupported file format. Only JPEG and PNG allowed.', + ar: 'تنسيق ملف غير مدعوم. يُسمح فقط بملفات JPEG و PNG.', + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From b3d478d6f4b9d96aec2fbcb53d4bb5de2d33ad2a Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 23 Jan 2026 07:53:50 +0200 Subject: [PATCH 063/210] Delete and Get Profile Picture for Doctor Endpoints --- src/controllers/doctor.controller.ts | 15 ++++++++++ src/routes/doctors.route.ts | 43 ++++++++++++++++++++++++++++ src/services/user.service.ts | 37 ++++++++++++++++++++++++ src/utils/errorMessages.ts | 4 +++ 4 files changed, 99 insertions(+) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index d116e84..1f31b98 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -56,4 +56,19 @@ export class DoctorController { } + public getProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + const profilePictureUrl = await this.userService.getUserProfilePicture(doctorId); + if(!profilePictureUrl) { + const error = createBilingualError(404, ErrorMessages.NO_PROFILE_PICTURE); + throw new HttpException(error.status, error.message, error.messageAr); + } + res.status(200).json({ data: { url: profilePictureUrl }, message: 'Profile picture retrieved successfully' }); + } + + public deleteProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + await this.userService.deleteProfilePicture(doctorId); + res.status(200).json({ message: 'Profile picture deleted successfully' }); + } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index b6203c3..4a8d5a7 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -126,5 +126,48 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(null, false, false, false, true), errorWrapper(this.doctorsController.updateProfilePicture) ); + this.router.get( + `/doctors/profile-picture`, + /* + #swagger.tags = ['Doctors'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get profile picture successful', + schema: { + data: { + url: 'https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg' + }, + message: 'Profile picture retrieved successfully' + } + } + */ + AuthMiddleware, + errorWrapper(this.doctorsController.getProfilePicture) + ) + this.router.delete( + `/doctors/profile-picture`, + /* + #swagger.tags = ['Doctors'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Profile picture deleted successfully', + schema: { + message: 'Profile picture deleted successfully' + } + } + */ + AuthMiddleware, + errorWrapper(this.doctorsController.deleteProfilePicture) + ); } } \ No newline at end of file diff --git a/src/services/user.service.ts b/src/services/user.service.ts index 2f4dd73..f189c4c 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -2,6 +2,8 @@ import cloudinary from "@/utils/cloudinary"; import { PrismaClient } from "@prisma/client"; import { Service } from "typedi"; import fs from "fs"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { HttpException } from "@/exceptions/HttpException"; const prisma = new PrismaClient(); @@ -34,4 +36,39 @@ export class UserService { publicId: uploadResult.public_id } } + + public async getUserProfilePicture(userId: string): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { photo_url: true } + }); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + return user?.photo_url || null; + } + + public async deleteProfilePicture(userId: string): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { photo_public_id: true } + }); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + if (user.photo_public_id) { + // Delete profile picture from Cloudinary + await cloudinary.uploader.destroy(user.photo_public_id); + // Update user record to remove photo info + await prisma.user.update({ + where: { id: userId }, + data: { + photo_url: null, + photo_public_id: null + } + }); + } + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 3269c7a..3a5db64 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -109,6 +109,10 @@ export const ErrorMessages = { en: 'Unsupported file format. Only JPEG and PNG allowed.', ar: 'تنسيق ملف غير مدعوم. يُسمح فقط بملفات JPEG و PNG.', }, + NO_PROFILE_PICTURE: { + en: 'No profile picture found', + ar: 'لم يتم العثور على صورة الملف الشخصي', + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From 029596bb8d4cf0db80fa084feec84335772d82dd Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 24 Jan 2026 00:14:02 +0200 Subject: [PATCH 064/210] swagger edit --- src/routes/doctors.route.ts | 9 ++++- src/swagger-output.json | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 4a8d5a7..66d3b18 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -109,6 +109,12 @@ export class DoctorsRoute implements Routes { /* #swagger.tags = ['Doctors'] #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.parameters['profilePicture'] = { in: 'formData', type: 'file', @@ -118,7 +124,8 @@ export class DoctorsRoute implements Routes { #swagger.responses[200] = { description: 'Profile picture updated successfully', schema: { - message: 'Profile picture updated successfully', + message: 'Profile picture updated successfully' + } } */ AuthMiddleware, diff --git a/src/swagger-output.json b/src/swagger-output.json index fda2821..084655f 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -2188,6 +2188,13 @@ "multipart/form-data" ], "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, { "name": "profilePicture", "in": "formData", @@ -2213,6 +2220,79 @@ } } } + }, + "get": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get profile picture successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" + } + } + }, + "message": { + "type": "string", + "example": "Profile picture retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Profile picture deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } } } } From 02a95eab34499ae0bad7a0850742007dadae0494 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 24 Jan 2026 01:12:54 +0200 Subject: [PATCH 065/210] create clinic by doctor endpoint --- src/config/prisma.ts | 5 +++ src/controllers/clinic.controller.ts | 27 ++++++++++++ src/dtos/clinics.dto.ts | 33 ++++++++++++++ src/prisma/schema.prisma | 15 ++++--- src/routes/clinic.route.ts | 26 +++++++++++ src/routes/doctors.route.ts | 6 ++- src/services/clinic.service.ts | 65 ++++++++++++++++++++++++++++ src/utils/errorMessages.ts | 6 ++- 8 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 src/config/prisma.ts create mode 100644 src/controllers/clinic.controller.ts create mode 100644 src/dtos/clinics.dto.ts create mode 100644 src/routes/clinic.route.ts create mode 100644 src/services/clinic.service.ts diff --git a/src/config/prisma.ts b/src/config/prisma.ts new file mode 100644 index 0000000..de08f2a --- /dev/null +++ b/src/config/prisma.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +export default prisma; \ No newline at end of file diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts new file mode 100644 index 0000000..cbf53f8 --- /dev/null +++ b/src/controllers/clinic.controller.ts @@ -0,0 +1,27 @@ +import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { HttpException } from "@/exceptions/HttpException"; +import { RequestWithUser } from "@/interfaces"; +import { ClinicService } from "@/services/clinic.service"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { NextFunction, Request, Response } from "express"; +import Container from "typedi"; + +export class ClinicController { + public clinicService = Container.get(ClinicService); + + public createClinic = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const clinicData: CreateClinicRequestDto = req.body; + + const isAllowedToCreateClinic = await this.clinicService.isDoctorAllowedToCreateClinic(req.user.id); + if (!isAllowedToCreateClinic) { + const error = createBilingualError(403, ErrorMessages.MAX_CLINICS_REACHED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const createdClinic = await this.clinicService.createClinic(req.user.id, clinicData); + + this.clinicService.linkDoctorToClinic(req.user.id, createdClinic, clinicData.fees); + + res.status(201).json({ message: 'Clinic created successfully' }); + } +} diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts new file mode 100644 index 0000000..2dff0c8 --- /dev/null +++ b/src/dtos/clinics.dto.ts @@ -0,0 +1,33 @@ +import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsString } from "class-validator"; + +export class CreateClinicRequestDto { + @IsString() + @IsNotEmpty() + public name: string; + + @IsNotEmpty() + @IsDate() + public opening_at: Date; + + @IsNotEmpty() + @IsDate() + public closing_at: Date; + + @IsString() + @IsNotEmpty() + public address: string; + + @IsString() + public address_maps_link?: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsBoolean() + public canPayOnline?: boolean; + + @IsNotEmpty() + @IsNumber() + fees: number; +} \ No newline at end of file diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index f984d9f..a806a49 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -51,13 +51,14 @@ model User { } model Doctor { - id String @id @default(uuid()) - specialization String @db.VarChar(255) - avg_time DateTime? @db.Time(0) - account_status DoctorAccountStatus @default(PENDING) + id String @id @default(uuid()) + specialization String @db.VarChar(255) + avg_time DateTime? @db.Time(0) + account_status DoctorAccountStatus @default(PENDING) + num_of_created_clinics Int @default(0) // Relations - user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) - clinic_doctors ClinicDoctor[] + user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) + clinic_doctors ClinicDoctor[] @@map("Doctor") } @@ -147,6 +148,7 @@ model ScanLab { model Clinic { id String @id @default(uuid()) + name String @db.VarChar(255) is_active Boolean @default(true) opening_at DateTime @db.Time(0) closing_at DateTime @db.Time(0) @@ -154,6 +156,7 @@ model Clinic { address_maps_link String? @db.VarChar(500) phone String @db.VarChar(20) canPayOnline Boolean @default(false) + created_by String @db.VarChar(255) created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts new file mode 100644 index 0000000..fdff097 --- /dev/null +++ b/src/routes/clinic.route.ts @@ -0,0 +1,26 @@ +import { ClinicController } from "@/controllers/clinic.controller"; +import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { Routes } from "@/interfaces"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Role } from "@prisma/client"; +import { Router } from "express"; + +export class ClinicRoute implements Routes { + public path = '/clinics' + public router = Router(); + public clinicController = new ClinicController(); + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.post( + `${this.path}`, + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + ValidationMiddleware(CreateClinicRequestDto), + this.clinicController.createClinic + ); + } +} \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 66d3b18..740d21c 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -4,8 +4,9 @@ import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; import { errorWrapper } from "@/utils/errorWrapper"; -import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import upload from "@/middlewares/multer.middleware"; +import { Role } from "@prisma/client"; export class DoctorsRoute implements Routes { @@ -102,6 +103,7 @@ export class DoctorsRoute implements Routes { */ ValidationMiddleware(DoctorSetPasswordRequestDto), AuthMiddleware, + RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.doctorSetPassword) ); this.router.patch( @@ -129,6 +131,7 @@ export class DoctorsRoute implements Routes { } */ AuthMiddleware, + RoleMiddleware(Role.DOCTOR), upload.single('profilePicture'), ValidationMiddleware(null, false, false, false, true), errorWrapper(this.doctorsController.updateProfilePicture) @@ -174,6 +177,7 @@ export class DoctorsRoute implements Routes { } */ AuthMiddleware, + RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.deleteProfilePicture) ); } diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts new file mode 100644 index 0000000..0832800 --- /dev/null +++ b/src/services/clinic.service.ts @@ -0,0 +1,65 @@ +import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { Service } from "typedi"; +import prisma from "@/config/prisma"; + +@Service() +export class ClinicService { + private MAX_CLINICS_PER_DOCTOR = 3; + + public async isDoctorAllowedToCreateClinic(doctorId: string): Promise { + const doctor = await prisma.doctor.findUnique({ + where: { + id: doctorId, + }, + select: { + num_of_created_clinics: true, + } + }); + if (!doctor) { + return false; + } + return doctor.num_of_created_clinics <= this.MAX_CLINICS_PER_DOCTOR; + } + + public async createClinic(doctorId: string, clinicData: CreateClinicRequestDto): Promise { + + const createdClinic = await prisma.clinic.create({ + data: { + name: clinicData.name, + opening_at: clinicData.opening_at, + closing_at: clinicData.closing_at, + address: clinicData.address, + address_maps_link: clinicData.address_maps_link, + phone: clinicData.phone, + canPayOnline: clinicData.canPayOnline, + created_by: doctorId, + }, + select: { + id: true, + } + }); + + return createdClinic.id; + } + + public async linkDoctorToClinic(doctorId: string, clinicId: string, fees: number): Promise { + await prisma.clinicDoctor.create({ + data: { + doctor_id: doctorId, + clinic_id: clinicId, + fees, + } + }); + + await prisma.doctor.update({ + where: { + id: doctorId, + }, + data: { + num_of_created_clinics: { + increment: 1, + } + } + }); + } +} \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 3a5db64..fa8236a 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -99,7 +99,10 @@ export const ErrorMessages = { en: 'Password has already been set', ar: 'تم تعيين كلمة المرور بالفعل', }, - + MAX_CLINICS_REACHED: { + en: 'Maximum number of created clinics reached', + ar: 'تم الوصول إلى الحد الأقصى لعدد العيادات', + }, // File upload errors NO_FILE_UPLOADED: { en: 'No file uploaded', @@ -113,6 +116,7 @@ export const ErrorMessages = { en: 'No profile picture found', ar: 'لم يتم العثور على صورة الملف الشخصي', }, + // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From fa61afac066c941e9b346cb63b67066980977faa Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 01:04:10 +0200 Subject: [PATCH 066/210] added get clinic by id endpoint and altered some fields in the db schema --- src/controllers/clinic.controller.ts | 12 ++++++++++ src/dtos/clinics.dto.ts | 18 ++++++++++----- .../migration.sql | 13 +++++++++++ .../migration.sql | 12 ++++++++++ src/prisma/schema.prisma | 4 ++-- src/routes/clinic.route.ts | 6 +++++ src/server.ts | 7 +++++- src/services/clinic.service.ts | 22 +++++++++++++++++++ src/utils/errorMessages.ts | 6 +++++ 9 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/prisma/migrations/20260124222051_clinic_update_schema/migration.sql create mode 100644 src/prisma/migrations/20260124224056_adjusted_time_to_string/migration.sql diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index cbf53f8..d38ff46 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -24,4 +24,16 @@ export class ClinicController { res.status(201).json({ message: 'Clinic created successfully' }); } + + public getClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + + const clinic = await this.clinicService.getClinicById(clinicId); + if (!clinic) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + res.status(200).json({ message: 'Clinic retrieved successfully', data: clinic }); + } } diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts index 2dff0c8..afb3590 100644 --- a/src/dtos/clinics.dto.ts +++ b/src/dtos/clinics.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsString } from "class-validator"; +import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsOptional, IsString, Matches } from "class-validator"; export class CreateClinicRequestDto { @IsString() @@ -6,17 +6,24 @@ export class CreateClinicRequestDto { public name: string; @IsNotEmpty() - @IsDate() - public opening_at: Date; + @IsString() + @Matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, { + message: 'opening_at must be in HH:MM format (e.g., 13:00)' + }) + public opening_at: string; @IsNotEmpty() - @IsDate() - public closing_at: Date; + @IsString() + @Matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, { + message: 'closing_at must be in HH:MM format (e.g., 13:00)' + }) + public closing_at: string; @IsString() @IsNotEmpty() public address: string; + @IsOptional() @IsString() public address_maps_link?: string; @@ -24,6 +31,7 @@ export class CreateClinicRequestDto { @IsNotEmpty() public phone: string; + @IsOptional() @IsBoolean() public canPayOnline?: boolean; diff --git a/src/prisma/migrations/20260124222051_clinic_update_schema/migration.sql b/src/prisma/migrations/20260124222051_clinic_update_schema/migration.sql new file mode 100644 index 0000000..8239d22 --- /dev/null +++ b/src/prisma/migrations/20260124222051_clinic_update_schema/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - Added the required column `created_by` to the `Clinic` table without a default value. This is not possible if the table is not empty. + - Added the required column `name` to the `Clinic` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Clinic" ADD COLUMN "created_by" VARCHAR(255) NOT NULL, +ADD COLUMN "name" VARCHAR(255) NOT NULL; + +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "num_of_created_clinics" INTEGER NOT NULL DEFAULT 0; diff --git a/src/prisma/migrations/20260124224056_adjusted_time_to_string/migration.sql b/src/prisma/migrations/20260124224056_adjusted_time_to_string/migration.sql new file mode 100644 index 0000000..61c18ae --- /dev/null +++ b/src/prisma/migrations/20260124224056_adjusted_time_to_string/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Changed the type of `opening_at` on the `Clinic` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + - Changed the type of `closing_at` on the `Clinic` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- AlterTable +ALTER TABLE "Clinic" DROP COLUMN "opening_at", +ADD COLUMN "opening_at" VARCHAR(12) NOT NULL, +DROP COLUMN "closing_at", +ADD COLUMN "closing_at" VARCHAR(12) NOT NULL; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index a806a49..0c52fe2 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -150,8 +150,8 @@ model Clinic { id String @id @default(uuid()) name String @db.VarChar(255) is_active Boolean @default(true) - opening_at DateTime @db.Time(0) - closing_at DateTime @db.Time(0) + opening_at String @db.VarChar(12) + closing_at String @db.VarChar(12) address String @db.VarChar(300) address_maps_link String? @db.VarChar(500) phone String @db.VarChar(20) diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index fdff097..723b291 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -22,5 +22,11 @@ export class ClinicRoute implements Routes { ValidationMiddleware(CreateClinicRequestDto), this.clinicController.createClinic ); + + this.router.get( + `${this.path}/:id`, + AuthMiddleware, // To be Discussed: Should patients be able to view clinic details? + this.clinicController.getClinicById + ); } } \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 28aa85b..ef92ba9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,8 +5,13 @@ import { ValidateEnv } from '@utils/validateEnv'; import { FabricRoute } from '@routes/fabric.route'; import { SuperAdminRoute } from './routes/superAdmin.route'; import { DoctorsRoute } from './routes/doctors.route'; +import { ClinicRoute } from './routes/clinic.route'; ValidateEnv(); -const app = new App([new AuthRoute(), new FabricRoute(), new AdminRoute() , new SuperAdminRoute(), new DoctorsRoute()]); +const app = new App( + [ + new AuthRoute(), new FabricRoute(), new AdminRoute(), + new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute() + ]); app.listen(); diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 0832800..4f8b7e6 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -1,6 +1,7 @@ import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; import { Service } from "typedi"; import prisma from "@/config/prisma"; +import { Clinic } from "@/interfaces"; @Service() export class ClinicService { @@ -62,4 +63,25 @@ export class ClinicService { } }); } + + public async getClinicById(clinicId: string): Promise | null> { + const clinic = await prisma.clinic.findUnique({ + where: { + id: clinicId, + }, + select: { + id: true, + is_active: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + phone: true, + canPayOnline: true, + created_at: true, + } + }); + + return clinic; + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index fa8236a..b32d43b 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -117,6 +117,12 @@ export const ErrorMessages = { ar: 'لم يتم العثور على صورة الملف الشخصي', }, + // Clinic errors + CLINIC_NOT_FOUND: { + en: 'Clinic not found', + ar: 'العيادة غير موجودة', + }, + // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From 6449e015404e3828dcea3d016b763f2f0236ea1e Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 01:21:33 +0200 Subject: [PATCH 067/210] update clinic by id endpoint --- src/controllers/clinic.controller.ts | 18 ++++++++++++++++-- src/dtos/clinics.dto.ts | 2 +- src/routes/clinic.route.ts | 14 +++++++++++--- src/routes/doctors.route.ts | 8 ++++++++ src/services/clinic.service.ts | 16 ++++++++++++++-- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index d38ff46..99a624c 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -1,4 +1,4 @@ -import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser } from "@/interfaces"; import { ClinicService } from "@/services/clinic.service"; @@ -10,7 +10,7 @@ export class ClinicController { public clinicService = Container.get(ClinicService); public createClinic = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { - const clinicData: CreateClinicRequestDto = req.body; + const clinicData: CreateUpdateClinicRequestDto = req.body; const isAllowedToCreateClinic = await this.clinicService.isDoctorAllowedToCreateClinic(req.user.id); if (!isAllowedToCreateClinic) { @@ -36,4 +36,18 @@ export class ClinicController { res.status(200).json({ message: 'Clinic retrieved successfully', data: clinic }); } + + public updateClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + const clinicUpdateData: CreateUpdateClinicRequestDto = req.body; + + const isClinicUpdated = await this.clinicService.updateClinic(clinicId, clinicUpdateData); + + if (!isClinicUpdated) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + res.status(200).json({ message: 'Clinic updated successfully' }); + } } diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts index afb3590..97f5966 100644 --- a/src/dtos/clinics.dto.ts +++ b/src/dtos/clinics.dto.ts @@ -1,6 +1,6 @@ import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsOptional, IsString, Matches } from "class-validator"; -export class CreateClinicRequestDto { +export class CreateUpdateClinicRequestDto { @IsString() @IsNotEmpty() public name: string; diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index 723b291..7aa3f7f 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -1,5 +1,5 @@ import { ClinicController } from "@/controllers/clinic.controller"; -import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Routes } from "@/interfaces"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; @@ -19,14 +19,22 @@ export class ClinicRoute implements Routes { `${this.path}`, AuthMiddleware, RoleMiddleware(Role.DOCTOR), - ValidationMiddleware(CreateClinicRequestDto), + ValidationMiddleware(CreateUpdateClinicRequestDto), this.clinicController.createClinic ); this.router.get( `${this.path}/:id`, AuthMiddleware, // To be Discussed: Should patients be able to view clinic details? - this.clinicController.getClinicById + this.clinicController.createClinic + ); + + this.router.patch( + `${this.path}/:id`, + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + ValidationMiddleware(CreateUpdateClinicRequestDto, true), + this.clinicController.updateClinicById ); } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 740d21c..3baa7f3 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -18,6 +18,8 @@ export class DoctorsRoute implements Routes { } private initializeRoutes() { + + // Doctor Signup Route this.router.post( `/doctors/signup`, /* @@ -46,6 +48,8 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(DoctorSignupRequestDto), errorWrapper(this.doctorsController.doctorSignup) ); + + // Doctor Login Route this.router.post( `/doctors/login`, /* @@ -82,6 +86,8 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(DoctorLoginRequestDto), errorWrapper(this.doctorsController.doctorLogin) ); + + // Doctor Set Password Route this.router.patch( `/doctors/set-password`, /* @@ -106,6 +112,8 @@ export class DoctorsRoute implements Routes { RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.doctorSetPassword) ); + + // Doctor Profile Picture Routes this.router.patch( `/doctors/profile-picture`, /* diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 4f8b7e6..d90e5aa 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -1,4 +1,4 @@ -import { CreateClinicRequestDto } from "@/dtos/clinics.dto"; +import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; @@ -22,7 +22,7 @@ export class ClinicService { return doctor.num_of_created_clinics <= this.MAX_CLINICS_PER_DOCTOR; } - public async createClinic(doctorId: string, clinicData: CreateClinicRequestDto): Promise { + public async createClinic(doctorId: string, clinicData: CreateUpdateClinicRequestDto): Promise { const createdClinic = await prisma.clinic.create({ data: { @@ -84,4 +84,16 @@ export class ClinicService { return clinic; } + + public async updateClinic(clinicId: string, clinicData: CreateUpdateClinicRequestDto): Promise { + const updatedClinic = await prisma.clinic.update({ + where: { + id: clinicId, + }, + data: { + ...clinicData, + }, + }); + return updatedClinic !== null; + } } \ No newline at end of file From 93e14c882964b284bc366e4fa7ec25de3064c336 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 01:38:08 +0200 Subject: [PATCH 068/210] Implemented delete Clinic by ID endpoint --- src/controllers/clinic.controller.ts | 16 +++++++- src/routes/clinic.route.ts | 7 ++++ src/services/clinic.service.ts | 55 ++++++++++++++++++++++++++++ src/utils/errorMessages.ts | 5 ++- 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 99a624c..810e320 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -42,7 +42,7 @@ export class ClinicController { const clinicUpdateData: CreateUpdateClinicRequestDto = req.body; const isClinicUpdated = await this.clinicService.updateClinic(clinicId, clinicUpdateData); - + if (!isClinicUpdated) { const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); @@ -50,4 +50,18 @@ export class ClinicController { res.status(200).json({ message: 'Clinic updated successfully' }); } + + public deleteClinicById = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + + const isCreatingDoctor = await this.clinicService.isCreatingDoctorOfClinic(req.user.id, clinicId); + + if (!isCreatingDoctor) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_CLINIC_DELETION); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.clinicService.deleteClinic(clinicId); + + res.status(200).json({ message: 'Clinic deleted successfully' }); + } } diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index 7aa3f7f..6c4801b 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -36,5 +36,12 @@ export class ClinicRoute implements Routes { ValidationMiddleware(CreateUpdateClinicRequestDto, true), this.clinicController.updateClinicById ); + + this.router.delete( + `${this.path}/:id`, + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.clinicController.deleteClinicById + ); } } \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index d90e5aa..fd1d17b 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -96,4 +96,59 @@ export class ClinicService { }); return updatedClinic !== null; } + + public async isCreatingDoctorOfClinic(doctorId: string, clinicId: string): Promise { + const clinic = await prisma.clinic.findUnique({ + where: { + id: clinicId, + }, + select: { + created_by: true, + } + }); + if (!clinic) { + return false; + } + return clinic.created_by === doctorId; + } + + public async deleteClinic(clinicId: string): Promise { + const clinicDoctors = await prisma.clinicDoctor.findMany({ + where: { + clinic_id: clinicId, + }, + select: { + doctor_id: true, + } + }); + const deletedClinic = await prisma.$transaction(async (tx) => { + await tx.clinicDoctor.deleteMany({ + where: { + clinic_id: clinicId, + }, + }); + await tx.clinicNurse.deleteMany({ + where: { + clinic_id: clinicId, + } + }); + await tx.clinic.delete({ + where: { + id: clinicId, + } + }); + await Promise.all(clinicDoctors.map(async (cd) => { + await tx.doctor.update({ + where: { + id: cd.doctor_id, + }, + data: { + num_of_created_clinics: { + decrement: 1, + } + } + }); + })); + }); + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index b32d43b..5c8cecb 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -122,7 +122,10 @@ export const ErrorMessages = { en: 'Clinic not found', ar: 'العيادة غير موجودة', }, - + UNAUTHORIZED_CLINIC_DELETION: { + en: 'You are not authorized to delete this clinic', + ar: 'ليس لديك صلاحية لحذف هذه العيادة', + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From 2cec93d8de1e8a7da207d4942d3b7efb5d3e5cd9 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 02:07:24 +0200 Subject: [PATCH 069/210] added swagger annotations to the clinic routes --- src/controllers/doctor.controller.ts | 12 +- src/routes/clinic.route.ts | 130 ++++++++- src/routes/doctors.route.ts | 40 +++ src/services/clinic.service.ts | 1 + src/services/doctor.service.ts | 33 ++- src/swagger-output.json | 382 +++++++++++++++++++++++++++ src/swagger.js | 3 +- 7 files changed, 595 insertions(+), 6 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 1f31b98..40e3f13 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -52,14 +52,14 @@ export class DoctorController { await this.doctorService.updateDoctorProfilePicture(doctorId, uploadResult.url, uploadResult.publicId); - res.status(200).json({ message: 'Profile picture updated successfully'}); - + res.status(200).json({ message: 'Profile picture updated successfully' }); + } public getProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { const doctorId = req.user?.id; const profilePictureUrl = await this.userService.getUserProfilePicture(doctorId); - if(!profilePictureUrl) { + if (!profilePictureUrl) { const error = createBilingualError(404, ErrorMessages.NO_PROFILE_PICTURE); throw new HttpException(error.status, error.message, error.messageAr); } @@ -71,4 +71,10 @@ export class DoctorController { await this.userService.deleteProfilePicture(doctorId); res.status(200).json({ message: 'Profile picture deleted successfully' }); } + + public getDoctorClinics = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + const clinics = await this.doctorService.getDoctorClinics(doctorId); + res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); + } } \ No newline at end of file diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index 6c4801b..ec191c6 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -17,6 +17,38 @@ export class ClinicRoute implements Routes { private initializeRoutes() { this.router.post( `${this.path}`, + /* + #swagger.path = '/clinics' + #swagger.method = 'post' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Clinic creation data', + required: true, + schema: { + $name: 'Downtown Medical Clinic', + $opening_at: '09:00', + $closing_at: '17:00', + $address: '123 Main Street, City Center', + address_maps_link: 'https://maps.google.com/?q=123+Main+Street', + $phone: '+1234567890', + canPayOnline: true, + $fees: 100 + } + } + #swagger.responses[201] = { + description: 'Clinic created successfully', + schema: { + message: 'Clinic created successfully' + } + } + */ AuthMiddleware, RoleMiddleware(Role.DOCTOR), ValidationMiddleware(CreateUpdateClinicRequestDto), @@ -25,12 +57,85 @@ export class ClinicRoute implements Routes { this.router.get( `${this.path}/:id`, + /* + #swagger.path = '/clinics/{id}' + #swagger.method = 'get' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic details retrieved successfully', + schema: { + data: { + id: 'clinic-uuid-123', + name: 'Downtown Medical Clinic', + is_active: true, + opening_at: '09:00', + closing_at: '17:00', + address: '123 Main Street, City Center', + address_maps_link: 'https://maps.google.com/?q=123+Main+Street', + phone: '+1234567890', + canPayOnline: true, + created_at: '2024-01-01T00:00:00.000Z' + }, + message: 'Clinic retrieved successfully' + } + } + */ AuthMiddleware, // To be Discussed: Should patients be able to view clinic details? - this.clinicController.createClinic + this.clinicController.getClinicById ); this.router.patch( `${this.path}/:id`, + /* + #swagger.path = '/clinics/{id}' + #swagger.method = 'patch' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic to update', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Clinic update data (all fields are optional)', + required: true, + schema: { + name: 'Downtown Medical Clinic - Updated', + opening_at: '08:00', + closing_at: '18:00', + address: '456 New Street, City Center', + address_maps_link: 'https://maps.google.com/?q=456+New+Street', + phone: '+1234567891', + canPayOnline: false, + fees: 150 + } + } + #swagger.responses[200] = { + description: 'Clinic updated successfully', + schema: { + message: 'Clinic updated successfully' + } + } + */ AuthMiddleware, RoleMiddleware(Role.DOCTOR), ValidationMiddleware(CreateUpdateClinicRequestDto, true), @@ -39,6 +144,29 @@ export class ClinicRoute implements Routes { this.router.delete( `${this.path}/:id`, + /* + #swagger.path = '/clinics/{id}' + #swagger.method = 'delete' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic to delete', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic deleted successfully', + schema: { + message: 'Clinic deleted successfully' + } + } + */ AuthMiddleware, RoleMiddleware(Role.DOCTOR), this.clinicController.deleteClinicById diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 3baa7f3..057b76a 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -188,5 +188,45 @@ export class DoctorsRoute implements Routes { RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.deleteProfilePicture) ); + + // Doctor's Clinic Routes + this.router.get( + `${this.path}/clinics`, + /* + #swagger.path = '/doctors/clinics' + #swagger.method = 'get' + #swagger.tags = ['Doctors'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get doctor clinics successful', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'Clinic Name', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + opening_at: '09:00', + closing_at: '17:00', + canPayOnline: true, + is_active: true, + created_at: '2024-01-01T00:00:00.000Z', + fees: 100 + } + ], + message: "Doctor's clinics retrieved successfully" + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + errorWrapper(this.doctorsController.getDoctorClinics) + ); } } \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index fd1d17b..7c964de 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -71,6 +71,7 @@ export class ClinicService { }, select: { id: true, + name: true, is_active: true, opening_at: true, closing_at: true, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 431188d..acff690 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -6,6 +6,7 @@ import { DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; import { DoctorLoginData } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; +import { Clinic } from "@/interfaces"; // TO BE CHANGED const prisma = new PrismaClient(); @@ -168,4 +169,34 @@ export class DoctorService { } }); } -} + + public async getDoctorClinics(doctorId: string): Promise[]> { + const clinics = await prisma.clinicDoctor.findMany({ + where: { + doctor_id: doctorId + }, + select: { + clinic: { + select: { + id: true, + name: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + phone: true, + is_active: true, + canPayOnline: true, + created_at: true, + } + }, + fees: true + } + }); + + return clinics.map(c => ({ + ...c.clinic, + fees: c.fees + })); + } +} \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 084655f..91c8185 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -27,6 +27,10 @@ { "name": "Doctors", "description": "Doctor account endpoints" + }, + { + "name": "Clinics", + "description": "Clinic endpoints" } ], "schemes": [ @@ -2294,6 +2298,384 @@ } } } + }, + "/doctors/clinics": { + "get": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get doctor clinics successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "is_active": { + "type": "boolean", + "example": true + }, + "created_at": { + "type": "string", + "example": "2024-01-01T00:00:00.000Z" + }, + "fees": { + "type": "number", + "example": 100 + } + } + } + }, + "message": { + "type": "string", + "example": "Doctor's clinics retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/clinics": { + "post": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Clinic creation data", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Downtown Medical Clinic" + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "fees": { + "type": "number", + "example": 100 + } + }, + "required": [ + "name", + "opening_at", + "closing_at", + "address", + "phone", + "fees" + ] + } + } + ], + "responses": { + "201": { + "description": "Clinic created successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic created successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/clinics/{id}": { + "get": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic details retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid-123" + }, + "name": { + "type": "string", + "example": "Downtown Medical Clinic" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "created_at": { + "type": "string", + "example": "2024-01-01T00:00:00.000Z" + } + } + }, + "message": { + "type": "string", + "example": "Clinic retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "patch": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to update" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Clinic update data (all fields are optional)", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Downtown Medical Clinic - Updated" + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "18:00" + }, + "address": { + "type": "string", + "example": "456 New Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=456+New+Street" + }, + "phone": { + "type": "string", + "example": "+1234567891" + }, + "canPayOnline": { + "type": "boolean", + "example": false + }, + "fees": { + "type": "number", + "example": 150 + } + } + } + } + ], + "responses": { + "200": { + "description": "Clinic updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to delete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index c62f8da..299e496 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -13,12 +13,13 @@ const doc = { { name: 'Super Admin', description: 'Super Admin endpoints' }, { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, { name: 'Doctors', description: 'Doctor account endpoints' }, + { name: 'Clinics', description: 'Clinic endpoints' }, ], }; const outputFile = './swagger-output.json'; const endpointsFiles = ['./src/routes/auth.route.ts', './src/routes/fabric.route.ts', './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts']; + './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts' , './src/routes/clinic.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From 5e1f90a9af0b503f4118f410947e302cf582abef Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 02:12:43 +0200 Subject: [PATCH 070/210] adjusted the code structure --- src/controllers/clinic.controller.ts | 6 + src/controllers/doctor.controller.ts | 6 - src/routes/clinic.route.ts | 39 ++++++ src/routes/doctors.route.ts | 40 ------- src/services/clinic.service.ts | 30 +++++ src/services/doctor.service.ts | 29 ----- src/swagger-output.json | 170 +++++++++++++-------------- 7 files changed, 159 insertions(+), 161 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 810e320..42aeaca 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -64,4 +64,10 @@ export class ClinicController { res.status(200).json({ message: 'Clinic deleted successfully' }); } + + public getDoctorClinics = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const doctorId = req.user?.id; + const clinics = await this.clinicService.getDoctorClinics(doctorId); + res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); + } } diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 40e3f13..3dde4cd 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -71,10 +71,4 @@ export class DoctorController { await this.userService.deleteProfilePicture(doctorId); res.status(200).json({ message: 'Profile picture deleted successfully' }); } - - public getDoctorClinics = async (req: RequestWithUser, res: Response, next: NextFunction) => { - const doctorId = req.user?.id; - const clinics = await this.doctorService.getDoctorClinics(doctorId); - res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); - } } \ No newline at end of file diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index ec191c6..2d10e5e 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -171,5 +171,44 @@ export class ClinicRoute implements Routes { RoleMiddleware(Role.DOCTOR), this.clinicController.deleteClinicById ); + + this.router.get( + `${this.path}`, + /* + #swagger.path = '/clinics' + #swagger.method = 'get' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get doctor clinics successful', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'Clinic Name', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + opening_at: '09:00', + closing_at: '17:00', + canPayOnline: true, + is_active: true, + created_at: '2024-01-01T00:00:00.000Z', + fees: 100 + } + ], + message: "Doctor's clinics retrieved successfully" + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.clinicController.getDoctorClinics + ); } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 057b76a..3baa7f3 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -188,45 +188,5 @@ export class DoctorsRoute implements Routes { RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.deleteProfilePicture) ); - - // Doctor's Clinic Routes - this.router.get( - `${this.path}/clinics`, - /* - #swagger.path = '/doctors/clinics' - #swagger.method = 'get' - #swagger.tags = ['Doctors'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Get doctor clinics successful', - schema: { - data: [ - { - id: 'clinic-uuid', - name: 'Clinic Name', - address: '123 Main St, City, Country', - address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', - phone: '1234567890', - opening_at: '09:00', - closing_at: '17:00', - canPayOnline: true, - is_active: true, - created_at: '2024-01-01T00:00:00.000Z', - fees: 100 - } - ], - message: "Doctor's clinics retrieved successfully" - } - } - */ - AuthMiddleware, - RoleMiddleware(Role.DOCTOR), - errorWrapper(this.doctorsController.getDoctorClinics) - ); } } \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 7c964de..8fa6108 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -152,4 +152,34 @@ export class ClinicService { })); }); } + + public async getDoctorClinics(doctorId: string): Promise[]> { + const clinics = await prisma.clinicDoctor.findMany({ + where: { + doctor_id: doctorId + }, + select: { + clinic: { + select: { + id: true, + name: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + phone: true, + is_active: true, + canPayOnline: true, + created_at: true, + } + }, + fees: true + } + }); + + return clinics.map(c => ({ + ...c.clinic, + fees: c.fees + })); + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index acff690..ed627f0 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -170,33 +170,4 @@ export class DoctorService { }); } - public async getDoctorClinics(doctorId: string): Promise[]> { - const clinics = await prisma.clinicDoctor.findMany({ - where: { - doctor_id: doctorId - }, - select: { - clinic: { - select: { - id: true, - name: true, - opening_at: true, - closing_at: true, - address: true, - address_maps_link: true, - phone: true, - is_active: true, - canPayOnline: true, - created_at: true, - } - }, - fees: true - } - }); - - return clinics.map(c => ({ - ...c.clinic, - fees: c.fees - })); - } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 91c8185..411441c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -2299,92 +2299,6 @@ } } }, - "/doctors/clinics": { - "get": { - "tags": [ - "Doctors" - ], - "description": "", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Get doctor clinics successful", - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "clinic-uuid" - }, - "name": { - "type": "string", - "example": "Clinic Name" - }, - "address": { - "type": "string", - "example": "123 Main St, City, Country" - }, - "address_maps_link": { - "type": "string", - "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" - }, - "phone": { - "type": "string", - "example": "1234567890" - }, - "opening_at": { - "type": "string", - "example": "09:00" - }, - "closing_at": { - "type": "string", - "example": "17:00" - }, - "canPayOnline": { - "type": "boolean", - "example": true - }, - "is_active": { - "type": "boolean", - "example": true - }, - "created_at": { - "type": "string", - "example": "2024-01-01T00:00:00.000Z" - }, - "fees": { - "type": "number", - "example": 100 - } - } - } - }, - "message": { - "type": "string", - "example": "Doctor's clinics retrieved successfully" - } - }, - "xml": { - "name": "main" - } - } - } - } - } - }, "/clinics": { "post": { "tags": [ @@ -2468,6 +2382,90 @@ } } } + }, + "get": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get doctor clinics successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "is_active": { + "type": "boolean", + "example": true + }, + "created_at": { + "type": "string", + "example": "2024-01-01T00:00:00.000Z" + }, + "fees": { + "type": "number", + "example": 100 + } + } + } + }, + "message": { + "type": "string", + "example": "Doctor's clinics retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } } }, "/clinics/{id}": { From 2d3fd8f73fdb9e7eb5e6933bbcdf1d04759dc7e9 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 21:11:10 +0200 Subject: [PATCH 071/210] adjusted swagger for set password for doctor endpoint --- src/routes/doctors.route.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 3baa7f3..1e00734 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -92,6 +92,12 @@ export class DoctorsRoute implements Routes { `/doctors/set-password`, /* #swagger.tags = ['Doctors'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.parameters['body'] = { in: 'body', description: 'New password data', From 668922fcee85894a8fa857921f4ad57cbf3c978f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sun, 25 Jan 2026 21:45:30 +0200 Subject: [PATCH 072/210] Update swagger docs --- src/swagger-output.json | 3003 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 3003 insertions(+) diff --git a/src/swagger-output.json b/src/swagger-output.json index e69de29..79906e6 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -0,0 +1,3003 @@ +{ + "swagger": "2.0", + "info": { + "title": "My API", + "description": "Description", + "version": "1.0.0" + }, + "host": "localhost:3000", + "basePath": "/", + "tags": [ + { + "name": "Auth", + "description": "Authentication and account endpoints" + }, + { + "name": "Admin", + "description": "Admin endpoints" + }, + { + "name": "Super Admin", + "description": "Super Admin endpoints" + }, + { + "name": "MedicalRecords", + "description": "Hyperledger Fabric medical record endpoints" + }, + { + "name": "Doctors", + "description": "Doctor account endpoints" + }, + { + "name": "Clinics", + "description": "Clinic endpoints" + } + ], + "schemes": [ + "http" + ], + "paths": { + "/auth/signup": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User signup data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "password": { + "type": "string", + "example": "password123" + } + }, + "required": [ + "email", + "name", + "phone", + "password" + ] + } + } + ], + "responses": { + "201": { + "description": "User successfully created", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "isEmailVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "gender": {}, + "date_of_birth": {}, + "role": { + "type": "string", + "example": "PATIENT" + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Signed Up Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "User login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "user@example.com" + }, + "password": { + "type": "string", + "example": "password123" + }, + "rememberMe": { + "type": "boolean", + "example": false + } + }, + "required": [ + "emailOrUsername", + "password" + ] + } + } + ], + "responses": { + "200": { + "description": "Login successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "role": { + "type": "string", + "example": "PATIENT" + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "string", + "example": "Cardiology" + }, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + } + } + }, + "message": { + "type": "string", + "example": "Logged In Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/logout": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Logout successful", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Logged Out Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "RefreshToken", + "in": "header", + "description": "Refresh token (sent via RefreshToken cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Token refreshed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": {} + }, + "accessToken": { + "type": "object", + "properties": { + "expiresIn": { + "type": "number", + "example": 3600 + }, + "expiresAt": { + "type": "string", + "example": "2025-12-12T12:00:00.000Z" + } + } + } + } + }, + "message": { + "type": "string", + "example": "Token Refreshed Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/complete-profile-info": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Complete user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "gender": { + "type": "string", + "example": "Male" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + } + }, + "required": [ + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Profile completed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Profile Completed Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/verify-otp": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Verify OTP", + "required": true, + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "example": "123456" + } + }, + "required": [ + "otp" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OTP verified successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "OTP Verified Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/forget-password": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Request password reset", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + } + }, + "required": [ + "email" + ] + } + } + ], + "responses": { + "200": { + "description": "Password reset email sent", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password Reset Email Sent Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/reset-password": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Reset password", + "required": true, + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "reset-token" + }, + "newPassword": { + "type": "string", + "example": "newPassword123" + } + }, + "required": [ + "token", + "newPassword" + ] + } + } + ], + "responses": { + "200": { + "description": "Password reset successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password Reset Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/resend-otp": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cooki)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OTP resent successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "OTP Resent Successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/google": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "302": { + "description": "Redirects to Google OAuth consent page" + } + } + } + }, + "/auth/google/callback": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "302": { + "description": "Redirects after Google authentication" + } + } + } + }, + "/auth/google/update-phone": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Update Google user phone", + "required": true, + "schema": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "example": "1234567890" + } + }, + "required": [ + "phone" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Phone number updated successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "example": "1234567890" + } + } + }, + "message": { + "type": "string", + "example": "Phone number updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/auth/google/userData": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "User data retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "user@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "username": { + "type": "string", + "example": "johndoe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + } + } + }, + "message": { + "type": "string", + "example": "User data retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/fabric/onboard": { + "post": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Identity onboarding data", + "required": true, + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "example": "org1" + }, + "mspId": { + "type": "string", + "example": "Org1MSP" + }, + "certificate": { + "type": "string", + "example": "PEM certificate" + }, + "privateKey": { + "type": "string", + "example": "PEM private key" + }, + "peerEndpoint": { + "type": "string", + "example": "localhost:7051" + }, + "peerHostAlias": { + "type": "string", + "example": "peer0.org1.example.com" + }, + "tlsCertificate": { + "type": "string", + "example": "PEM TLS certificate" + }, + "channelName": { + "type": "string", + "example": "mychannel" + }, + "chaincodeName": { + "type": "string", + "example": "test" + } + }, + "required": [ + "label", + "mspId", + "certificate", + "privateKey", + "peerEndpoint", + "peerHostAlias", + "tlsCertificate" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/identities": { + "get": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/identities/{label}": { + "delete": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "label", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/connections": { + "get": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/fabric/init-ledger": { + "post": { + "tags": [ + "FabricIdentity" + ], + "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/records": { + "get": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + }, + "post": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Medical record data", + "required": true, + "schema": { + "type": "object", + "properties": { + "patientId": { + "type": "string", + "example": "P12345" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "dateOfBirth": { + "type": "string", + "example": "1990-01-01" + }, + "gender": { + "type": "string", + "example": "Male" + }, + "bloodType": { + "type": "string", + "example": "O+" + }, + "ipfsCid": { + "type": "string", + "example": "Qm..." + }, + "summary": { + "type": "string", + "example": "Optional summary" + } + }, + "required": [ + "patientId", + "firstName", + "lastName", + "dateOfBirth", + "gender", + "bloodType", + "ipfsCid" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/records/health": { + "get": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/records/{patientId}": { + "get": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "" + } + } + }, + "put": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Update medical record data", + "required": true, + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "dateOfBirth": { + "type": "string", + "example": "1990-01-01" + }, + "gender": { + "type": "string", + "example": "Male" + }, + "bloodType": { + "type": "string", + "example": "O+" + }, + "ipfsCid": { + "type": "string", + "example": "Qm..." + }, + "summary": { + "type": "string", + "example": "Optional summary" + } + }, + "required": [ + "firstName", + "lastName", + "dateOfBirth", + "gender", + "bloodType", + "ipfsCid" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/records/{patientId}/access": { + "post": { + "tags": [ + "MedicalRecords" + ], + "description": "", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "X-Fabric-Identity", + "in": "header", + "description": "Identity label (e.g., org1)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Grant access to MSP", + "required": true, + "schema": { + "type": "object", + "properties": { + "targetMsp": { + "type": "string", + "example": "Org2MSP" + } + }, + "required": [ + "targetMsp" + ] + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/admin/doctors": { + "post": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "date_of_birth", + "specialization" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Doctor added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "PENDING" + } + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + }, + "photoUrl": {} + } + } + }, + "message": { + "type": "string", + "example": "Doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/doctors/unverified": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Unverified doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "photoUrl": {}, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + } + } + } + }, + "message": { + "type": "string", + "example": "Unverified doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/doctors/verify/{id}": { + "patch": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "body", + "in": "body", + "description": "Verification status", + "required": true, + "schema": { + "type": "object", + "properties": { + "isApproved": { + "type": "boolean", + "example": true + } + }, + "required": [ + "isApproved" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor verification status updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Doctor verification status updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/doctors/{id}": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/admins": { + "post": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Admin data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "password": { + "type": "string", + "example": "SecurePass123!" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + } + }, + "required": [ + "email", + "name", + "password", + "phone", + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Admin added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Admin added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Admins retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + } + }, + "message": { + "type": "string", + "example": "Admins retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/admins/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Admin ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Admin retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "admin@example.com" + }, + "name": { + "type": "string", + "example": "Jane Smith" + }, + "role": { + "type": "string", + "example": "ADMIN" + }, + "username": { + "type": "string", + "example": "janesmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01T00:00:00.000Z" + }, + "photo_url": {}, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + } + } + }, + "message": { + "type": "string", + "example": "Admin retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/doctors": { + "post": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "date_of_birth", + "specialization" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Doctor added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {} + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor added successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "Approved" + } + } + }, + "photoUrl": {} + } + } + }, + "message": { + "type": "string", + "example": "Doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/doctors/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "role": { + "type": "string", + "example": "DOCTOR" + }, + "username": { + "type": "string", + "example": "smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "object", + "properties": { + "key": { + "type": "string", + "example": "CARDIOLOGY" + }, + "value": { + "type": "string", + "example": "Cardiology" + } + } + }, + "avg_time": {}, + "account_status": { + "type": "string", + "example": "Approved" + } + } + }, + "photoUrl": {} + } + }, + "message": { + "type": "string", + "example": "Doctor retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/doctors/signup": { + "post": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor signup data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "doctor@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "password": { + "type": "string", + "example": "SecurePassword123" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "specialization": { + "type": "string", + "example": "CARDIOLOGY or امراض القلب or Cardiology" + } + }, + "required": [ + "email", + "name", + "phone", + "password", + "gender", + "specialization" + ] + } + } + ], + "responses": { + "201": { + "description": "Doctor signup successful", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Doctor registered successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/doctors/login": { + "post": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Doctor login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "doctor@example.com" + }, + "password": { + "type": "string", + "example": "SecurePassword123" + }, + "rememberMe": { + "type": "string", + "example": "true" + } + }, + "required": [ + "emailOrUsername", + "password", + "rememberMe" + ] + } + } + ], + "responses": { + "200": { + "description": "Doctor login successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1 + }, + "email": { + "type": "string", + "example": "test@example.com" + }, + "name": { + "type": "string", + "example": "Dr. Smith" + }, + "username": { + "type": "string", + "example": "drsmith" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "doctor": { + "type": "object", + "properties": { + "specialization": { + "type": "string", + "example": "CARDIOLOGY" + }, + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + } + } + }, + "message": { + "type": "string", + "example": "Doctor logged in successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/doctors/set-password": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "New password data", + "required": true, + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "NewSecurePassword123" + } + }, + "required": [ + "password" + ] + } + } + ], + "responses": { + "200": { + "description": "Password set successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/doctors/profile-picture": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "profilePicture", + "in": "formData", + "type": "file", + "required": true, + "description": "Profile picture file" + } + ], + "responses": { + "200": { + "description": "Profile picture updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get profile picture successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" + } + } + }, + "message": { + "type": "string", + "example": "Profile picture retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Doctors" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Profile picture deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/clinics": { + "post": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Clinic creation data", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Downtown Medical Clinic" + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "fees": { + "type": "number", + "example": 100 + } + }, + "required": [ + "name", + "opening_at", + "closing_at", + "address", + "phone", + "fees" + ] + } + } + ], + "responses": { + "201": { + "description": "Clinic created successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic created successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get doctor clinics successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "is_active": { + "type": "boolean", + "example": true + }, + "created_at": { + "type": "string", + "example": "2024-01-01T00:00:00.000Z" + }, + "fees": { + "type": "number", + "example": 100 + } + } + } + }, + "message": { + "type": "string", + "example": "Doctor's clinics retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/clinics/{id}": { + "get": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic details retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid-123" + }, + "name": { + "type": "string", + "example": "Downtown Medical Clinic" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "created_at": { + "type": "string", + "example": "2024-01-01T00:00:00.000Z" + } + } + }, + "message": { + "type": "string", + "example": "Clinic retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "patch": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to update" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Clinic update data (all fields are optional)", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Downtown Medical Clinic - Updated" + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "18:00" + }, + "address": { + "type": "string", + "example": "456 New Street, City Center" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=456+New+Street" + }, + "phone": { + "type": "string", + "example": "+1234567891" + }, + "canPayOnline": { + "type": "boolean", + "example": false + }, + "fees": { + "type": "number", + "example": 150 + } + } + } + } + ], + "responses": { + "200": { + "description": "Clinic updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to delete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Clinic deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + } + } +} \ No newline at end of file From b63ae4dbbf173388e940e2c83dbcf66226fae40b Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 26 Jan 2026 00:44:39 +0200 Subject: [PATCH 073/210] =?UTF-8?q?schema=20updates=20for=C2=A0appointment?= =?UTF-8?q?s=20and=20doctor=20/=20create=20related=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/appointments.interface.ts | 23 +- src/interfaces/doctor-schedule.interface.ts | 16 ++ src/interfaces/index.ts | 6 + .../migration.sql | 71 ++++++ src/prisma/schema.prisma | 235 ++++++++++-------- 5 files changed, 250 insertions(+), 101 deletions(-) create mode 100644 src/interfaces/doctor-schedule.interface.ts create mode 100644 src/prisma/migrations/20260125212701_appointments_modifications/migration.sql diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 3ae1c06..bfcf431 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -1,17 +1,38 @@ import { User } from './users.interface'; +import {AppointmentStatus} from '@prisma/client' export interface Appointment { id: string; patient_id: string; doctor_id: string; + clinic_id: string | null; scheduled_time: Date; + slot_duration: Date; + end_time: Date; is_online: boolean; is_completed: boolean; estimated_time?: number; + status: AppointmentStatus; + cancelled_by: string | null; created_at: Date; modified_at: Date; deleted_at?: Date; patient: User; doctor: User; -} \ No newline at end of file +} + + +export interface AvailableDay { + date: string; + day_of_week: string; + available_slots_count: number; +} + +export interface AvailableSlot { + start_time: string; + end_time: string; +} + + + diff --git a/src/interfaces/doctor-schedule.interface.ts b/src/interfaces/doctor-schedule.interface.ts new file mode 100644 index 0000000..e0ee844 --- /dev/null +++ b/src/interfaces/doctor-schedule.interface.ts @@ -0,0 +1,16 @@ +import { DayOfWeek} from "@prisma/client"; + +export interface DoctorSchedule { + id: string; + doctor_id: string; + clinic_id: string | null; + day_of_week: DayOfWeek; + start_time: Date; + end_time: Date; + slot_duration: number; + buffer_time: number; + is_active: boolean; + created_at: Date; + modified_at: Date; + deleted_at: Date | null; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 8f9c26b..52bad34 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -27,3 +27,9 @@ export * from './audit-logs.interface'; // Medical Records export * from './medical-records.interface'; + +// Appointments +export * from './appointments.interface'; + +// Doctor Schedule +export * from './doctor-schedule.interface'; diff --git a/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql b/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql new file mode 100644 index 0000000..c71c689 --- /dev/null +++ b/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql @@ -0,0 +1,71 @@ +/* + Warnings: + + - Added the required column `end_time` to the `Appointments` table without a default value. This is not possible if the table is not empty. + - Added the required column `slot_duration` to the `Appointments` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "AvailabilityType" AS ENUM ('UNSET', 'ONLINE', 'OFFLINE', 'BOTH'); + +-- CreateEnum +CREATE TYPE "DayOfWeek" AS ENUM ('SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'); + +-- CreateEnum +CREATE TYPE "AppointmentStatus" AS ENUM ('CONFIRMED', 'COMPLETED', 'CANCELLED', 'NO_SHOW'); + +-- AlterTable +ALTER TABLE "Appointments" ADD COLUMN "cancelled_by" TEXT, +ADD COLUMN "clinic_id" TEXT, +ADD COLUMN "end_time" TIMESTAMP(3) NOT NULL, +ADD COLUMN "slot_duration" TIMESTAMP(3) NOT NULL, +ADD COLUMN "status" "AppointmentStatus" NOT NULL DEFAULT 'CONFIRMED'; + +-- AlterTable +ALTER TABLE "ClinicDoctor" ADD COLUMN "is_accepting" BOOLEAN NOT NULL DEFAULT true; + +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "availability_type" "AvailabilityType" NOT NULL DEFAULT 'UNSET', +ADD COLUMN "present" BOOLEAN NOT NULL DEFAULT true; + +-- CreateTable +CREATE TABLE "DoctorSchedules" ( + "id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "clinic_id" TEXT, + "day_of_week" "DayOfWeek" NOT NULL, + "start_time" TIME(0) NOT NULL, + "end_time" TIME(0) NOT NULL, + "slot_duration" INTEGER NOT NULL, + "buffer_time" INTEGER NOT NULL DEFAULT 0, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "DoctorSchedules_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_doctor_id_idx" ON "DoctorSchedules"("doctor_id"); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_clinic_id_idx" ON "DoctorSchedules"("clinic_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "DoctorSchedules_doctor_id_clinic_id_day_of_week_key" ON "DoctorSchedules"("doctor_id", "clinic_id", "day_of_week"); + +-- CreateIndex +CREATE INDEX "Appointments_clinic_id_idx" ON "Appointments"("clinic_id"); + +-- CreateIndex +CREATE INDEX "Appointments_status_idx" ON "Appointments"("status"); + +-- AddForeignKey +ALTER TABLE "Appointments" ADD CONSTRAINT "Appointments_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 0c52fe2..441ebd8 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } @@ -11,41 +8,39 @@ datasource db { } model User { - id String @id @default(uuid()) - name String @db.VarChar(255) - email String @unique @db.VarChar(255) - username String @unique @db.VarChar(255) - phone String @db.VarChar(20) - password_hash String @db.VarChar(255) + id String @id @default(uuid()) + name String @db.VarChar(255) + email String @unique @db.VarChar(255) + username String @unique @db.VarChar(255) + phone String @db.VarChar(20) + password_hash String @db.VarChar(255) gender Gender date_of_birth DateTime - role Role @default(PATIENT) - isVerified Boolean @default(false) - hasCompletedProfile Boolean @default(false) - email_OTP String? @db.VarChar(6) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + email_OTP String? @db.VarChar(6) email_OTP_expires_at DateTime? - password_reset_token String? @db.VarChar(255) + isVerified Boolean @default(false) + password_reset_token String? @db.VarChar(255) password_reset_token_expires_at DateTime? - photo_url String? @db.VarChar(500) - photo_public_id String? @db.VarChar(500) - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? - - // Relations - patient Patient? @relation("UserAsPatient") - doctor Doctor? @relation("UserAsDoctor") - appointments_as_patient Appointment[] @relation("PatientAppointments") - appointments_as_doctor Appointment[] @relation("DoctorAppointments") - medications_as_patient Medication[] @relation("PatientMedications") - medications_as_doctor Medication[] @relation("DoctorMedications") - scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") - scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") - clinics_as_nurse ClinicNurse[] @relation("NurseClinics") - audit_logs AuditLog[] @relation("UserAuditLogs") - controlled_patients Patient[] @relation("ControllingNurse") - refresh_tokens RefreshToken[] @relation("UserRefreshTokens") - medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") + role Role @default(PATIENT) + hasCompletedProfile Boolean @default(false) + photo_public_id String? @db.VarChar(500) + photo_url String? @db.VarChar(500) + appointments_as_doctor Appointment[] @relation("DoctorAppointments") + appointments_as_patient Appointment[] @relation("PatientAppointments") + audit_logs AuditLog[] @relation("UserAuditLogs") + clinics_as_nurse ClinicNurse[] @relation("NurseClinics") + doctor Doctor? @relation("UserAsDoctor") + medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") + medications_as_doctor Medication[] @relation("DoctorMedications") + medications_as_patient Medication[] @relation("PatientMedications") + controlled_patients Patient[] @relation("ControllingNurse") + patient Patient? @relation("UserAsPatient") + refresh_tokens RefreshToken[] @relation("UserRefreshTokens") + scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") + scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") @@map("Users") } @@ -56,45 +51,51 @@ model Doctor { avg_time DateTime? @db.Time(0) account_status DoctorAccountStatus @default(PENDING) num_of_created_clinics Int @default(0) - // Relations - user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) + availability_type AvailabilityType @default(UNSET) + present Boolean @default(true) clinic_doctors ClinicDoctor[] + user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) + doctorSchedules DoctorSchedule[] @@map("Doctor") } model Patient { - id String @id @default(uuid()) - bc_address String @db.VarChar(255) - consent Boolean @default(false) - controlling_nurse_id String? - - // Relations - user User @relation("UserAsPatient", fields: [id], references: [id], onDelete: Cascade) - controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id], onDelete: SetNull) + id String @id @default(uuid()) + bc_address String @db.VarChar(255) + consent Boolean @default(false) + controlling_nurse_id String? + controlling_nurse_user User? @relation("ControllingNurse", fields: [controlling_nurse_id], references: [id]) + user User @relation("UserAsPatient", fields: [id], references: [id], onDelete: Cascade) @@map("Patient") } model Appointment { - id String @id @default(uuid()) + id String @id @default(uuid()) patient_id String? doctor_id String? scheduled_time DateTime - is_online Boolean @default(false) - is_completed Boolean @default(false) + is_online Boolean @default(false) + is_completed Boolean @default(false) estimated_time Float? - created_at DateTime @default(now()) - modified_at DateTime @updatedAt + created_at DateTime @default(now()) + modified_at DateTime @updatedAt deleted_at DateTime? - - // Relations - patient User? @relation("PatientAppointments", fields: [patient_id], references: [id], onDelete: Restrict) - doctor User? @relation("DoctorAppointments", fields: [doctor_id], references: [id], onDelete: Restrict) + cancelled_by String? + clinic_id String? + end_time DateTime + slot_duration DateTime + status AppointmentStatus @default(CONFIRMED) + clinic Clinic? @relation(fields: [clinic_id], references: [id]) + doctor User? @relation("DoctorAppointments", fields: [doctor_id], references: [id], onDelete: Restrict) + patient User? @relation("PatientAppointments", fields: [patient_id], references: [id], onDelete: Restrict) @@index([patient_id]) @@index([doctor_id]) @@index([scheduled_time]) + @@index([clinic_id]) + @@index([status]) @@map("Appointments") } @@ -108,14 +109,12 @@ model Medication { medication_start_time DateTime @db.Time(0) frequency Int period Period - description String? @db.Text + description String? created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - - // Relations - patient User @relation("PatientMedications", fields: [patient_id], references: [id], onDelete: Restrict) - doctor User @relation("DoctorMedications", fields: [doctor_id], references: [id], onDelete: Restrict) + doctor User @relation("DoctorMedications", fields: [doctor_id], references: [id]) + patient User @relation("PatientMedications", fields: [patient_id], references: [id]) @@index([patient_id]) @@index([doctor_id]) @@ -131,15 +130,13 @@ model ScanLab { scheduled_time DateTime? @db.Time(0) frequency Int? period Period? - description String? @db.Text + description String? type ScanLabType created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - - // Relations - patient User @relation("PatientScansLabs", fields: [patient_id], references: [id], onDelete: Restrict) - doctor User @relation("DoctorScansLabs", fields: [doctor_id], references: [id], onDelete: Restrict) + doctor User @relation("DoctorScansLabs", fields: [doctor_id], references: [id]) + patient User @relation("PatientScansLabs", fields: [patient_id], references: [id]) @@index([patient_id]) @@index([doctor_id]) @@ -147,23 +144,23 @@ model ScanLab { } model Clinic { - id String @id @default(uuid()) - name String @db.VarChar(255) - is_active Boolean @default(true) - opening_at String @db.VarChar(12) - closing_at String @db.VarChar(12) - address String @db.VarChar(300) - address_maps_link String? @db.VarChar(500) - phone String @db.VarChar(20) - canPayOnline Boolean @default(false) - created_by String @db.VarChar(255) - created_at DateTime @default(now()) - modified_at DateTime @updatedAt + id String @id @default(uuid()) + is_active Boolean @default(true) + address String @db.VarChar(300) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt deleted_at DateTime? - - // Relations - clinic_nurses ClinicNurse[] - clinic_doctors ClinicDoctor[] + address_maps_link String? @db.VarChar(500) + canPayOnline Boolean @default(false) + phone String @db.VarChar(20) + created_by String @db.VarChar(255) + name String @db.VarChar(255) + opening_at String @db.VarChar(12) + closing_at String @db.VarChar(12) + appointments Appointment[] + clinic_doctors ClinicDoctor[] + clinic_nurses ClinicNurse[] + doctorSchedules DoctorSchedule[] @@map("Clinic") } @@ -172,10 +169,8 @@ model ClinicNurse { id String @id @default(uuid()) clinic_id String nurse_id String - - // Relations - clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) - nurse User @relation("NurseClinics", fields: [nurse_id], references: [id], onDelete: Cascade) + clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + nurse User @relation("NurseClinics", fields: [nurse_id], references: [id], onDelete: Cascade) @@unique([clinic_id, nurse_id]) @@index([nurse_id]) @@ -183,14 +178,13 @@ model ClinicNurse { } model ClinicDoctor { - id String @id @default(uuid()) - clinic_id String - doctor_id String - fees Float - - // Relations - clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) - doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + id String @id @default(uuid()) + clinic_id String + doctor_id String + fees Float + is_accepting Boolean @default(true) + clinic Clinic @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) @@unique([clinic_id, doctor_id]) @@index([doctor_id]) @@ -203,9 +197,7 @@ model AuditLog { action Action bc_hash String @db.VarChar(255) created_at DateTime @default(now()) - - // Relations - user User @relation("UserAuditLogs", fields: [user_id], references: [id], onDelete: Restrict) + user User @relation("UserAuditLogs", fields: [user_id], references: [id]) @@index([user_id]) @@index([created_at]) @@ -222,8 +214,7 @@ model MedicalRecord { created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - - patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id], onDelete: Restrict) + patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id]) @@index([patient_id]) @@index([doctor_id]) @@ -239,9 +230,7 @@ model RefreshToken { is_revoked Boolean @default(false) created_at DateTime @default(now()) revoked_at DateTime? - - // Relations - user User @relation("UserRefreshTokens", fields: [user_id], references: [id], onDelete: Cascade) + user User @relation("UserRefreshTokens", fields: [user_id], references: [id], onDelete: Cascade) @@index([user_id]) @@index([token_hash]) @@ -249,6 +238,28 @@ model RefreshToken { @@map("RefreshTokens") } +model DoctorSchedule { + id String @id @default(uuid()) + doctor_id String + clinic_id String? + day_of_week DayOfWeek + start_time DateTime @db.Time(0) + end_time DateTime @db.Time(0) + slot_duration Int + buffer_time Int @default(0) + is_active Boolean @default(true) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + + @@unique([doctor_id, clinic_id, day_of_week]) + @@index([doctor_id]) + @@index([clinic_id]) + @@map("DoctorSchedules") +} + enum ScanLabType { SCAN LAB @@ -276,11 +287,11 @@ enum Gender { } enum Role { - SUPER_ADMIN ADMIN DOCTOR NURSE PATIENT + SUPER_ADMIN } enum RecordType { @@ -295,3 +306,27 @@ enum DoctorAccountStatus { APPROVED REJECTED } + +enum AvailabilityType { + UNSET + ONLINE + OFFLINE + BOTH +} + +enum DayOfWeek { + SUNDAY + MONDAY + TUESDAY + WEDNESDAY + THURSDAY + FRIDAY + SATURDAY +} + +enum AppointmentStatus { + CONFIRMED + COMPLETED + CANCELLED + NO_SHOW +} From 973114de2fd3e68481baaa5c9985e1ed5aa2a5ad Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 26 Jan 2026 19:19:41 +0200 Subject: [PATCH 074/210] dto/ main base with clinics route --- docker-compose.yml | 2 + package-lock.json | 10 ++- package.json | 3 +- src/app.ts | 3 +- src/controllers/appointment.controller.ts | 23 +++++++ src/controllers/clinic.controller.ts | 5 ++ src/dtos/appointments.dto.ts | 28 +++++++++ src/interfaces/appointments.interface.ts | 3 + src/interfaces/clinics.interface.ts | 4 +- src/interfaces/doctors.interface.ts | 11 +++- src/prisma/schema.prisma | 2 +- src/routes/appointment.route.ts | 75 +++++++++++++++++++++++ src/server.ts | 4 +- src/services/appointment.service.ts | 10 +++ src/services/clinic.service.ts | 22 +++++++ src/swagger-output.json | 69 +++++++++++++++++++++ src/swagger.js | 3 +- 17 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 src/controllers/appointment.controller.ts create mode 100644 src/dtos/appointments.dto.ts create mode 100644 src/routes/appointment.route.ts create mode 100644 src/services/appointment.service.ts diff --git a/docker-compose.yml b/docker-compose.yml index 0c62b9d..a32ece4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ services: postgres: container_name: postgres_db image: postgres:16 + ports: + - "5432:5432" environment: - POSTGRES_USER=NG - POSTGRES_PASSWORD=password diff --git a/package-lock.json b/package-lock.json index df2d0cc..718daed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,10 +32,8 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", - "multer": "^2.0.2", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", - "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -78,6 +76,7 @@ "pm2": "^6.0.13", "prettier": "^3.6.2", "supertest": "^7.1.4", + "swagger-autogen": "^2.23.7", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "tsc-alias": "^1.8.16", @@ -5979,6 +5978,7 @@ }, "node_modules/deepmerge": { "version": "4.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9135,6 +9135,7 @@ }, "node_modules/json5": { "version": "2.2.3", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -12552,6 +12553,7 @@ "version": "2.23.7", "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz", "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^7.4.1", @@ -12564,6 +12566,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -12576,6 +12579,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12587,6 +12591,7 @@ "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", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12607,6 +12612,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" diff --git a/package.json b/package.json index 2b7798a..d7469f6 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,8 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", - "multer": "^2.0.2", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", - "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -93,6 +91,7 @@ "pm2": "^6.0.13", "prettier": "^3.6.2", "supertest": "^7.1.4", + "swagger-autogen": "^2.23.7", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "tsc-alias": "^1.8.16", diff --git a/src/app.ts b/src/app.ts index b1a2910..46a9889 100644 --- a/src/app.ts +++ b/src/app.ts @@ -27,8 +27,9 @@ export class App { this.initializeMiddlewares(); this.initializeRoutes(routes); - this.initializeSwagger(); this.initializeErrorHandling(); + this.initializeSwagger(); + } public listen() { diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts new file mode 100644 index 0000000..caf6a97 --- /dev/null +++ b/src/controllers/appointment.controller.ts @@ -0,0 +1,23 @@ +import { Request, Response, NextFunction } from "express"; +import { HttpException } from "@/exceptions/HttpException"; +import { catchAsync } from '@/utils/catchAsync'; + + + +export class AppointmentController { + + // get all clinics --> clinic crud + + // get all doctors in a clinic --> clinic crud + + // get online doctors --> doctors crud + + // get available days (at least one slot) + + // get available slots for a selected day + + // book a new appointment + + + +} \ No newline at end of file diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 42aeaca..a7dc19a 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -70,4 +70,9 @@ export class ClinicController { const clinics = await this.clinicService.getDoctorClinics(doctorId); res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); } + + public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { + const clinics = await this.clinicService.getActiveClinics(); + res.status(200).json({ data: clinics, message: 'Clinics retrieved successfully' }); + } } diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts new file mode 100644 index 0000000..865877d --- /dev/null +++ b/src/dtos/appointments.dto.ts @@ -0,0 +1,28 @@ +import { IsDateString, IsEnum, IsNotEmpty, IsString, Validate, ValidateIf } from "class-validator"; + +export class GetAvailableDaysDto { + @IsEnum(['ONLINE', 'OFFLINE']) + @IsNotEmpty() + appointment_type: 'ONLINE' | 'OFFLINE'; + + @ValidateIf(o => o.appointment_type == 'OFFLINE') + @IsString() + @IsNotEmpty() + clinic_id?: string; + + @IsString() + @IsNotEmpty() + doctor_id?: string; +} + +export class GetAvailableSlotsDto { + @IsDateString() + @IsNotEmpty() + date: string; +} + +export class CreateAppointmentDto { + @IsDateString() + @IsNotEmpty() + scheduled_time: string; +} diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index bfcf431..73f8857 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -22,6 +22,9 @@ export interface Appointment { doctor: User; } +export interface AvailableDays{ + dates: string[]; +} export interface AvailableDay { date: string; diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index 14e3875..9e1000f 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -3,8 +3,8 @@ import { User, Doctor } from './users.interface'; export interface Clinic { id: string; is_active: boolean; - opening_at: Date; - closing_at: Date; + opening_at: string; + closing_at: string; address: string; created_at: Date; modified_at: Date; diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts index 5cdcf6d..c7da247 100644 --- a/src/interfaces/doctors.interface.ts +++ b/src/interfaces/doctors.interface.ts @@ -1,4 +1,13 @@ -import { DoctorAccountStatus } from "@prisma/client"; +import { DoctorAccountStatus, AvailabilityType } from "@prisma/client"; + +export interface Doctor { + id: string; + avg_time?: Date | null; + account_status: DoctorAccountStatus; + num_of_created_clinics: number; + availability_type: AvailabilityType; + present: boolean; +} export interface DoctorLoginData { id: string, diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 441ebd8..6e3afc2 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -85,7 +85,7 @@ model Appointment { cancelled_by String? clinic_id String? end_time DateTime - slot_duration DateTime + slot_duration Int status AppointmentStatus @default(CONFIRMED) clinic Clinic? @relation(fields: [clinic_id], references: [id]) doctor User? @relation("DoctorAppointments", fields: [doctor_id], references: [id], onDelete: Restrict) diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts new file mode 100644 index 0000000..c28cf44 --- /dev/null +++ b/src/routes/appointment.route.ts @@ -0,0 +1,75 @@ +import { Routes } from "@/interfaces"; +import { Router } from "express"; +import { ClinicController } from "@/controllers/clinic.controller"; + + +export class AppointmentRoute implements Routes { + public path = '/appointments'; + public router = Router(); + clinicController = new ClinicController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + // // get online doctors + // this.router.get( + // `${this.path}/online-doctors` + // ); + + // get all clinics + this.router.get( + `${this.path}/clinics`, + /* + #swagger.path = '/appointments/clinics' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all clinics available for booking appointments' + #swagger.responses[200] = { + description: 'Active clinics retrieved successfully', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'New Cairo Medical Clinic', + opening_at: '10:00', + closing_at: '17:00', + address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.google.com/?q=123+Main+Street', + phone: '+1234567890', + canPayOnline: true + } + ], + message: 'Clinics retrieved successfully' + } + } + */ + this.clinicController.getActiveClinics + ); + + // // get all doctors in a clinic + // this.router.get( + // `${this.path}/clinic/:clinicId/doctors`, + // ); + + // // get available days + // this.router.get( + // `${this.path}/available-days`, + + // ); + + // // get all available slots + // this.router.get( + // `${this.path}/available-slots` + // ); + + // // book appointment + // this.router.post( + // `${this.path}/book-appointment` + // ) + } + + +} + diff --git a/src/server.ts b/src/server.ts index ef92ba9..7edad22 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,12 +6,14 @@ import { FabricRoute } from '@routes/fabric.route'; import { SuperAdminRoute } from './routes/superAdmin.route'; import { DoctorsRoute } from './routes/doctors.route'; import { ClinicRoute } from './routes/clinic.route'; +import { AppointmentRoute } from './routes/appointment.route'; + ValidateEnv(); const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), - new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute() + new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute() ]); app.listen(); diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts new file mode 100644 index 0000000..b3bc3da --- /dev/null +++ b/src/services/appointment.service.ts @@ -0,0 +1,10 @@ +import { HttpException } from '@/exceptions/HttpException'; +import prisma from '@/config/prisma'; + + + +export class AppointmentService { + + // get all clinics + +} \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 8fa6108..223205b 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -182,4 +182,26 @@ export class ClinicService { fees: c.fees })); } + + public async getActiveClinics(): Promise[]> { + const clinics = await prisma.clinic.findMany({ + where:{ + is_active: true, + deleted_at: null, + }, + select:{ + id: true, + name: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + phone: true, + canPayOnline: true, + } + }); + return clinics.map(c => ({ + ...c + })); + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 79906e6..9bde8d7 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -31,6 +31,10 @@ { "name": "Clinics", "description": "Clinic endpoints" + }, + { + "name": "Appointments", + "description": "Appointment endpoints" } ], "schemes": [ @@ -2998,6 +3002,71 @@ } } } + }, + "/appointments/clinics": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all clinics available for booking appointments", + "responses": { + "200": { + "description": "Active clinics retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "New Cairo Medical Clinic" + }, + "opening_at": { + "type": "string", + "example": "10:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, Medical Park" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + } + } + } + }, + "message": { + "type": "string", + "example": "Clinics retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index 848306b..b980b76 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,12 +14,13 @@ const doc = { { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, { name: 'Doctors', description: 'Doctor account endpoints' }, { name: 'Clinics', description: 'Clinic endpoints' }, + { name: 'Appointments', description: 'Appointment endpoints' }, ], }; const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts' , './src/routes/clinic.route.ts']; + './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts' , './src/routes/clinic.route.ts', './src/routes/appointment.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From 2598ed9f3d036ab29a09683aa45f8f101c4a583c Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 27 Jan 2026 00:38:34 +0200 Subject: [PATCH 075/210] doctor/clinic stuff for appointment booking get all online doctors route get all doctors for a selected clinic --- src/controllers/clinic.controller.ts | 7 +++ src/controllers/doctor.controller.ts | 6 ++ src/routes/appointment.route.ts | 63 ++++++++++++++++--- src/services/clinic.service.ts | 34 +++++++++++ src/services/doctor.service.ts | 27 ++++++++- src/swagger-output.json | 91 ++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 11 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index a7dc19a..62760fc 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -71,6 +71,13 @@ export class ClinicController { res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); } + public getClinicDoctors = async (req: Request, res: Response, next: NextFunction) => { + const {clinicId} = req.params; + const doctors = await this.clinicService.getClinicDoctors(clinicId); + res.status(200).json({ data: doctors, message: 'Clinic doctors retrieved successfully' }); + + } + public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { const clinics = await this.clinicService.getActiveClinics(); res.status(200).json({ data: clinics, message: 'Clinics retrieved successfully' }); diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 3dde4cd..892d026 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -71,4 +71,10 @@ export class DoctorController { await this.userService.deleteProfilePicture(doctorId); res.status(200).json({ message: 'Profile picture deleted successfully' }); } + + public getOnlineDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { + const doctors = await this.doctorService.getOnlineDoctors(); + res.status(200).json({ data: doctors, message: 'Online doctors retrieved successfully' }); + } + } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index c28cf44..7413a80 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -1,22 +1,42 @@ import { Routes } from "@/interfaces"; import { Router } from "express"; import { ClinicController } from "@/controllers/clinic.controller"; - +import { DoctorController } from "@/controllers/doctor.controller"; export class AppointmentRoute implements Routes { public path = '/appointments'; public router = Router(); clinicController = new ClinicController(); + doctorController = new DoctorController(); constructor() { this.initializeRoutes(); } private initializeRoutes() { - // // get online doctors - // this.router.get( - // `${this.path}/online-doctors` - // ); + // get online doctors + this.router.get( + `${this.path}/online-doctors`, + /* + #swagger.path = '/appointments/online-doctors' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all available online doctors' + #swagger.responses[200] = { + description: 'Online doctors retrieved successfully', + schema: { + data: [ + { + id: 'doctor-uuid', + name: 'House' + } + ], + message: 'Online doctors retrieved successfully' + } + } + */ + this.doctorController.getOnlineDoctors + ); // get all clinics this.router.get( @@ -48,10 +68,35 @@ export class AppointmentRoute implements Routes { this.clinicController.getActiveClinics ); - // // get all doctors in a clinic - // this.router.get( - // `${this.path}/clinic/:clinicId/doctors`, - // ); + // get all doctors in a clinic + this.router.get( + `${this.path}/clinic/:clinicId/doctors`, + /* + #swagger.path = '/appointments/clinic/{clinicId}/doctors' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'Clinic ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic doctors retrieved successfully', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'House' + } + ], + message: 'Clinic doctors retrieved successfully' + } + } + */ + this.clinicController.getClinicDoctors + ); // // get available days // this.router.get( diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 223205b..7c08fa0 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -2,6 +2,7 @@ import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; +import { Doctor } from "@prisma/client"; @Service() export class ClinicService { @@ -183,6 +184,39 @@ export class ClinicService { })); } + public async getClinicDoctors(clinicId: string): Promise[]> { + const doctors = await prisma.clinicDoctor.findMany({ + where: { + clinic_id: clinicId, + is_accepting: true, + doctor: { + account_status: 'APPROVED', + present: true, + availability_type: { + in: ['OFFLINE', 'BOTH'] + }, + }, + }, + include: { + doctor:{ + include:{ + user:{ + select:{ + id: true, + name: true + }, + }, + }, + }, + }, + }); + + return doctors.map(d => ({ + id: d.doctor.id, + name: d.doctor.user.name, + })); + } + public async getActiveClinics(): Promise[]> { const clinics = await prisma.clinic.findMany({ where:{ diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index ed627f0..8f3ab34 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -2,11 +2,10 @@ import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dt import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; -import { DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; +import { Doctor, DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; import { DoctorLoginData } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; -import { Clinic } from "@/interfaces"; // TO BE CHANGED const prisma = new PrismaClient(); @@ -170,4 +169,28 @@ export class DoctorService { }); } + public async getOnlineDoctors(): Promise[]> { + const doctors = await prisma.doctor.findMany({ + where:{ + account_status: 'APPROVED', + present: true, + availability_type: { + in: ['ONLINE', 'BOTH'] + } + }, + select:{ + id: true, + user: { + select:{ + name: true, + } + } + } + }); + return doctors.map(doctor => ({ + id: doctor.id, + name: doctor.user.name, + })); + } + } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 9bde8d7..6416452 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3003,6 +3003,47 @@ } } }, + "/appointments/online-doctors": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all available online doctors", + "responses": { + "200": { + "description": "Online doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "doctor-uuid" + }, + "name": { + "type": "string", + "example": "House" + } + } + } + }, + "message": { + "type": "string", + "example": "Online doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/appointments/clinics": { "get": { "tags": [ @@ -3067,6 +3108,56 @@ } } } + }, + "/appointments/clinic/{clinicId}/doctors": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all doctors who are accepting appointments at a selected clinic", + "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "Clinic ID" + } + ], + "responses": { + "200": { + "description": "Clinic doctors retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "House" + } + } + } + }, + "message": { + "type": "string", + "example": "Clinic doctors retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file From 67d8c53e53c197978e8ffa0abbaeaad62b9567c1 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 27 Jan 2026 03:47:36 +0200 Subject: [PATCH 076/210] appointment service (days) --- src/interfaces/appointments.interface.ts | 12 +-- src/routes/appointment.route.ts | 2 - src/services/appointment.service.ts | 109 ++++++++++++++++++++++- 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 73f8857..a7a5ef0 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -1,5 +1,5 @@ import { User } from './users.interface'; -import {AppointmentStatus} from '@prisma/client' +import {AppointmentStatus, DayOfWeek} from '@prisma/client' export interface Appointment { id: string; @@ -22,14 +22,10 @@ export interface Appointment { doctor: User; } -export interface AvailableDays{ - dates: string[]; -} - export interface AvailableDay { - date: string; - day_of_week: string; - available_slots_count: number; + date: string; + dayOfWeek: DayOfWeek; + displayDate: string; } export interface AvailableSlot { diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 7413a80..a2a0cf7 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -114,7 +114,5 @@ export class AppointmentRoute implements Routes { // `${this.path}/book-appointment` // ) } - - } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index b3bc3da..e54e65e 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1,10 +1,113 @@ -import { HttpException } from '@/exceptions/HttpException'; import prisma from '@/config/prisma'; +import { DayOfWeek } from '@prisma/client'; +import { AvailableDay } from '@/interfaces'; export class AppointmentService { - // get all clinics + public async getAvailableDays(doctorId: string, clinicId: string | null, isOnline: boolean): Promise{ + const daysAhead = 60 + const availableDays: AvailableDay[] = []; -} \ No newline at end of file + const schedules = await prisma.doctorSchedule.findMany({ + where:{ + doctor_id: doctorId, + clinic_id: clinicId, + is_active: true, + deleted_at: null + }, + select:{ + day_of_week: true, + start_time: true, + end_time: true, + slot_duration: true, + buffer_time: true + } + }); + + if (schedules.length === 0){ + return []; + } + + /* + creates a map --> avoid searching through schedules every time we need to check if a doctor works on a specific day + key = day of week , value = the schedule object for that day + + instead of  + { day_of_week: 'MONDAY', start_time: '09:00', end_time: '17:00', slot_duration: 20, buffer_time: 10 } + + will be: + 'MONDAY' => { start_time: '09:00', end_time: '17:00', ... } + */ + const scheduleMap = new Map(); + schedules.forEach(schedule => { + scheduleMap.set(schedule.day_of_week, schedule); + }); + + const today = new Date(); + today.setHours(0,0,0,0); + + for (let i=1; i<= daysAhead; i++){ + // create a copy from today --> if we used today directly it will be modified to today + 1 --> tomorrow date + const currentDate = new Date(today); + currentDate.setDate(today.getDate() + i); // current day now is = today + 1 + + const dayOfWeek = this.getDayOfWeek(currentDate.getDay()); + const schedule = scheduleMap.get(dayOfWeek); + + // skip if doctor doesnt work on this day + if (!schedule){ + continue; + } + + const hasAvailableSlots = true; + + if (hasAvailableSlots){ + availableDays.push({ + date: this.formatDate(currentDate), + dayOfWeek: dayOfWeek, + displayDate: this.formatDisplayDate(currentDate) + }); + } + } + return availableDays; + } + + // converts js representation of days (0-6) to prisma's enum + private getDayOfWeek(jsDay: number): DayOfWeek { + const days: DayOfWeek[] = [ + DayOfWeek.SUNDAY, + DayOfWeek.MONDAY, + DayOfWeek.TUESDAY, + DayOfWeek.WEDNESDAY, + DayOfWeek.THURSDAY, + DayOfWeek.FRIDAY, + DayOfWeek.SATURDAY, + ]; + return days[jsDay]; + } + + // format date as YYYY-MM-DD + private formatDate(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + // format date for display (sun, jan20, 2026) + private formatDisplayDate(date: Date): string { + const options: Intl.DateTimeFormatOptions = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }; + // later --> for arabic ar-EG + return date.toLocaleDateString('en-EG', options); + } + + + +} From 580c44d85cf24bf7ec78ede4ef8f56e7837b0606 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 27 Jan 2026 21:28:51 +0200 Subject: [PATCH 077/210] available days/slots in appointments --- src/controllers/appointment.controller.ts | 29 ++- src/interfaces/appointments.interface.ts | 12 +- .../migration.sql | 9 + src/routes/appointment.route.ts | 196 +++++++++++++----- src/services/appointment.service.ts | 136 ++++++++++-- src/swagger-output.json | 135 ++++++++++++ 6 files changed, 449 insertions(+), 68 deletions(-) create mode 100644 src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index caf6a97..c90f41f 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -1,20 +1,37 @@ import { Request, Response, NextFunction } from "express"; +import { RequestWithUser } from "@/interfaces"; import { HttpException } from "@/exceptions/HttpException"; import { catchAsync } from '@/utils/catchAsync'; +import { AppointmentService } from "@/services/appointment.service" +import Container from "typedi"; +export class AppointmentController { + public appointmentService = Container.get(AppointmentService); -export class AppointmentController { + public getAvailableDays = catchAsync(async (req: Request, res: Response): Promise => { + const { doctorId } = req.params; + const { clinicId } = req.query; - // get all clinics --> clinic crud + const availableDays = await this.appointmentService.getAvailableDays(doctorId, clinicId as string || null) + res.status(200).json({ + data: availableDays, + // message: 'Available days retrieved successfully', + }); - // get all doctors in a clinic --> clinic crud + }); - // get online doctors --> doctors crud + public getAvailableSlots = catchAsync(async (req: Request, res: Response): Promise => { + const { doctorId } = req.params; + const { date, clinicId } = req.query; - // get available days (at least one slot) + const availableSlots = await this.appointmentService.getAvailableSlots(doctorId, clinicId as string || null, date as string) + res.status(200).json({ + data: availableSlots, + // message: 'Available slots retrieved successfully', + }); - // get available slots for a selected day + }); // book a new appointment diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index a7a5ef0..f65fe29 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -28,9 +28,15 @@ export interface AvailableDay { displayDate: string; } -export interface AvailableSlot { - start_time: string; - end_time: string; +// export interface AvailableSlot { +// start_time: string; +// end_time: string; +// } + +export interface TimeSlot { + start: string; + end: string; + available: boolean; } diff --git a/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql b/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql new file mode 100644 index 0000000..50499f2 --- /dev/null +++ b/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - Changed the type of `slot_duration` on the `Appointments` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- AlterTable +ALTER TABLE "Appointments" DROP COLUMN "slot_duration", +ADD COLUMN "slot_duration" INTEGER NOT NULL; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index a2a0cf7..5711f8c 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -2,12 +2,14 @@ import { Routes } from "@/interfaces"; import { Router } from "express"; import { ClinicController } from "@/controllers/clinic.controller"; import { DoctorController } from "@/controllers/doctor.controller"; +import { AppointmentController } from "@/controllers/appointment.controller"; export class AppointmentRoute implements Routes { public path = '/appointments'; public router = Router(); clinicController = new ClinicController(); doctorController = new DoctorController(); + appointmentController = new AppointmentController(); constructor() { this.initializeRoutes(); @@ -18,23 +20,23 @@ export class AppointmentRoute implements Routes { this.router.get( `${this.path}/online-doctors`, /* - #swagger.path = '/appointments/online-doctors' - #swagger.method = 'get' - #swagger.tags = ['Appointments'] - #swagger.description = 'Get all available online doctors' - #swagger.responses[200] = { - description: 'Online doctors retrieved successfully', - schema: { - data: [ - { - id: 'doctor-uuid', - name: 'House' - } - ], - message: 'Online doctors retrieved successfully' + #swagger.path = '/appointments/online-doctors' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all available online doctors' + #swagger.responses[200] = { + description: 'Online doctors retrieved successfully', + schema: { + data: [ + { + id: 'doctor-uuid', + name: 'House' + } + ], + message: 'Online doctors retrieved successfully' + } } - } - */ + */ this.doctorController.getOnlineDoctors ); @@ -72,42 +74,142 @@ export class AppointmentRoute implements Routes { this.router.get( `${this.path}/clinic/:clinicId/doctors`, /* - #swagger.path = '/appointments/clinic/{clinicId}/doctors' - #swagger.method = 'get' - #swagger.tags = ['Appointments'] - #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' - #swagger.parameters['clinicId'] = { - in: 'path', - description: 'Clinic ID', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Clinic doctors retrieved successfully', - schema: { - data: [ - { - id: 'clinic-uuid', - name: 'House' - } - ], - message: 'Clinic doctors retrieved successfully' + #swagger.path = '/appointments/clinic/{clinicId}/doctors' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'Clinic ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic doctors retrieved successfully', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'House' + } + ], + message: 'Clinic doctors retrieved successfully' + } } - } - */ + */ this.clinicController.getClinicDoctors ); - // // get available days - // this.router.get( - // `${this.path}/available-days`, - - // ); + // get available days + this.router.get( + `${this.path}/doctor/:doctorId/available-days`, + /* + #swagger.path = '/appointments/doctor/{doctorId}/available-days' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all available days for a doctor that have at least one available slot' + #swagger.parameters['doctorId'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['clinicId'] = { + in: 'query', + description: 'Clinic ID (required for offline appointments)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Available days retrieved successfully', + schema: { + data: [ + { + date: '2026-02-03', + dayOfWeek: 'MONDAY', + displayDate: 'Monday, February 3, 2026' + }, + { + date: '2026-02-05', + dayOfWeek: 'WEDNESDAY', + displayDate: 'Wednesday, February 5, 2026' + }, + { + date: '2026-02-10', + dayOfWeek: 'MONDAY', + displayDate: 'Monday, February 10, 2026' + } + ], + message: 'Available days retrieved successfully', + } + } + #swagger.responses[400] = { + description: 'Bad request - missing required parameters' + } + #swagger.responses[404] = { + description: 'Doctor not found or not available' + } + */ + this.appointmentController.getAvailableDays + ); - // // get all available slots - // this.router.get( - // `${this.path}/available-slots` - // ); + // get all available slots + this.router.get( + `${this.path}/doctor/:doctorId/available-slots`, + /* + #swagger.path = '/appointments/doctor/{doctorId}/available-slots' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.description = 'Get all available time slots for a doctor on a specific date' + #swagger.parameters['doctorId'] = { + in: 'path', + description: 'Doctor ID', + required: true, + type: 'string' + } + #swagger.parameters['date'] = { + in: 'query', + description: 'Date in YYYY-MM-DD format', + required: true, + type: 'string', + example: '2026-02-03' + } + #swagger.parameters['clinicId'] = { + in: 'query', + description: 'Clinic ID (required for offline appointments)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Available slots retrieved successfully', + schema: { + data: [ + { + start: '09:00', + end: '09:20' + }, + { + start: '09:30', + end: '09:50' + }, + { + start: '10:00', + end: '10:20' + }, + { + start: '10:30', + end: '10:50' + } + ], + message: 'Available slots retrieved successfully', + } + } + #swagger.responses[400] = { + description: 'Bad request - missing required parameters or invalid date' + } + */ + this.appointmentController.getAvailableSlots + ); // // book appointment // this.router.post( diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index e54e65e..3623fd4 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1,19 +1,23 @@ import prisma from '@/config/prisma'; import { DayOfWeek } from '@prisma/client'; import { AvailableDay } from '@/interfaces'; +import { Service } from 'typedi'; +import { TimeSlot } from '@/interfaces'; +import { logger } from '@/utils/logger'; - +@Service() export class AppointmentService { - public async getAvailableDays(doctorId: string, clinicId: string | null, isOnline: boolean): Promise{ - const daysAhead = 60 + public async getAvailableDays(doctorId: string, clinicId: string | null): Promise{ + const daysAhead = 30 const availableDays: AvailableDay[] = []; + const isOnline = true; const schedules = await prisma.doctorSchedule.findMany({ where:{ doctor_id: doctorId, - clinic_id: clinicId, + clinic_id: isOnline ? null : clinicId, is_active: true, deleted_at: null }, @@ -34,11 +38,8 @@ export class AppointmentService { creates a map --> avoid searching through schedules every time we need to check if a doctor works on a specific day key = day of week , value = the schedule object for that day - instead of  - { day_of_week: 'MONDAY', start_time: '09:00', end_time: '17:00', slot_duration: 20, buffer_time: 10 } - - will be: - 'MONDAY' => { start_time: '09:00', end_time: '17:00', ... } + instead of --> { day_of_week: 'MONDAY', start_time: '09:00', end_time: '17:00', slot_duration: 20, buffer_time: 10 } + will be --> 'MONDAY' => { start_time: '09:00', end_time: '17:00', ... } */ const scheduleMap = new Map(); schedules.forEach(schedule => { @@ -62,7 +63,7 @@ export class AppointmentService { } const hasAvailableSlots = true; - + if (hasAvailableSlots){ availableDays.push({ date: this.formatDate(currentDate), @@ -74,6 +75,101 @@ export class AppointmentService { return availableDays; } + public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise[]>{ + const requestedDate = new Date(date); + const dayOfWeek = this.getDayOfWeek(requestedDate.getDay()); + const isOnline = true; + + const schedule = await prisma.doctorSchedule.findFirst({ + where:{ + day_of_week: dayOfWeek, + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + is_active: true, + deleted_at: null, + }, + select:{ + start_time: true, + end_time: true, + slot_duration: true, + buffer_time: true, + } + }); + + if (!schedule) { + return []; + } + + const allSlots = this.generateTimeSlots(schedule.start_time, schedule.end_time, schedule.slot_duration, schedule.buffer_time); + + const startOfDay = new Date(requestedDate); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(requestedDate); + endOfDay.setHours(23, 59, 59, 999); + + const existingAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay, + }, + status: { + in: ['CONFIRMED', 'COMPLETED'] + }, + deleted_at: null, + }, + select: { + scheduled_time: true, + end_time: true, + } + }); + + const availableSlots = allSlots.filter(slot => { + const slotStart = this.parseTimeToDate(requestedDate, slot.start); + const slotEnd = this.parseTimeToDate(requestedDate, slot.end); + + const isBooked = existingAppointments.some(appointment => { + const appointmentStart = new Date(appointment.scheduled_time); + const appointmentEnd = new Date(appointment.end_time); + + return this.doesSlotOverlap(slotStart, slotEnd, appointmentStart, appointmentEnd); + }); + + // check if the slot is in the past (now the time is x, we cant book a slot before x) + const now = new Date(); + const isInPast = slotEnd <= now; + + return !isBooked && !isInPast; + }); + + return availableSlots; + } + + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ + const slots: Omit[] = []; + const start = new Date(startTime); + const end = new Date(endTime); + + let currentTime = new Date(start); + + while (currentTime < end){ + const slotEnd = new Date(currentTime.getTime() + slotDuration * 60000); + if (slotEnd <= end){ + slots.push({ + start: this.formatTime(currentTime), + end: this.formatTime(slotEnd), + }); + } + // move to next slot (slot duration + buffer time) + currentTime = new Date(currentTime.getTime() + (slotDuration + bufferTime) * 60000); + } + + return slots; + } + // converts js representation of days (0-6) to prisma's enum private getDayOfWeek(jsDay: number): DayOfWeek { const days: DayOfWeek[] = [ @@ -108,6 +204,22 @@ export class AppointmentService { return date.toLocaleDateString('en-EG', options); } - + // extract time from date / ex: 1970-01-01T09:00:00.000Z --> 09:00 + private formatTime(date: Date): string { + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${hours}:${minutes}`; + } -} + // date: 2026-01-27, time string: 10:30 --> 2026-01-27 10:30:00 + private parseTimeToDate(date: Date, timeStr: string): Date { + const [hours, minutes] = timeStr.split(':').map(Number); + const result = new Date(date); + result.setHours(hours, minutes, 0, 0); + return result; + } + + private doesSlotOverlap(slotStart: Date, slotEnd: Date, appointmentStart: Date, appointmentEnd: Date): boolean { + return (slotStart < appointmentEnd && slotEnd > appointmentStart); + } +} \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 6416452..efb9ec6 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3158,6 +3158,141 @@ } } } + }, + "/appointments/doctor/{doctorId}/available-days": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all available days for a doctor that have at least one available slot", + "parameters": [ + { + "name": "doctorId", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "clinicId", + "in": "query", + "description": "Clinic ID (required for offline appointments)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Available days retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "example": "2026-02-10" + }, + "dayOfWeek": { + "type": "string", + "example": "MONDAY" + }, + "displayDate": { + "type": "string", + "example": "Monday, February 10, 2026" + } + } + } + }, + "message": { + "type": "string", + "example": "Available days retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing required parameters" + }, + "404": { + "description": "Doctor not found or not available" + } + } + } + }, + "/appointments/doctor/{doctorId}/available-slots": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all available time slots for a doctor on a specific date", + "parameters": [ + { + "name": "doctorId", + "in": "path", + "required": true, + "type": "string", + "description": "Doctor ID" + }, + { + "name": "date", + "in": "query", + "description": "Date in YYYY-MM-DD format", + "required": true, + "type": "string", + "example": "2026-02-03" + }, + { + "name": "clinicId", + "in": "query", + "description": "Clinic ID (required for offline appointments)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Available slots retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "string", + "example": "10:30" + }, + "end": { + "type": "string", + "example": "10:50" + } + } + } + }, + "message": { + "type": "string", + "example": "Available slots retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing required parameters or invalid date" + } + } + } } } } \ No newline at end of file From 49402e79608ef0284db1d45ee6cdc713471fead6 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 27 Jan 2026 22:57:40 +0200 Subject: [PATCH 078/210] Separated Profile Picture into user service to be general for all users roles and updated the swagger --- src/controllers/doctor.controller.ts | 31 ---- src/controllers/user.controller.ts | 42 +++++ src/routes/doctors.route.ts | 76 --------- src/routes/user.route.ts | 95 +++++++++++ src/server.ts | 4 +- src/services/doctor.service.ts | 10 -- src/services/user.service.ts | 20 ++- src/swagger-output.json | 238 ++++++++++++++------------- src/swagger.js | 6 +- 9 files changed, 277 insertions(+), 245 deletions(-) create mode 100644 src/controllers/user.controller.ts create mode 100644 src/routes/user.route.ts diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 3dde4cd..fe1d49e 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -40,35 +40,4 @@ export class DoctorController { res.status(200).json({ message: 'Password set successfully' }); } - public updateProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { - const doctorId = req.user?.id; - const profilePictureFile = req.file; - - if (!profilePictureFile) { - const error = createBilingualError(400, ErrorMessages.NO_FILE_UPLOADED); - throw new HttpException(error.status, error.message, error.messageAr); - } - const uploadResult = await this.userService.updateProfilePicture(doctorId, profilePictureFile.path); - - await this.doctorService.updateDoctorProfilePicture(doctorId, uploadResult.url, uploadResult.publicId); - - res.status(200).json({ message: 'Profile picture updated successfully' }); - - } - - public getProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { - const doctorId = req.user?.id; - const profilePictureUrl = await this.userService.getUserProfilePicture(doctorId); - if (!profilePictureUrl) { - const error = createBilingualError(404, ErrorMessages.NO_PROFILE_PICTURE); - throw new HttpException(error.status, error.message, error.messageAr); - } - res.status(200).json({ data: { url: profilePictureUrl }, message: 'Profile picture retrieved successfully' }); - } - - public deleteProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { - const doctorId = req.user?.id; - await this.userService.deleteProfilePicture(doctorId); - res.status(200).json({ message: 'Profile picture deleted successfully' }); - } } \ No newline at end of file diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts new file mode 100644 index 0000000..86f0fc0 --- /dev/null +++ b/src/controllers/user.controller.ts @@ -0,0 +1,42 @@ +import { HttpException } from "@/exceptions/HttpException"; +import { RequestWithUser } from "@/interfaces"; +import { UserService } from "@/services/user.service"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { NextFunction, Request, Response } from "express"; +import { Container } from "typedi"; + + +export class UsersController { + public userService = Container.get(UserService); + + public updateProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + const userRole = req.user?.role; + const profilePictureFile = req.file; + + if (!profilePictureFile) { + const error = createBilingualError(400, ErrorMessages.NO_FILE_UPLOADED); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.userService.updateProfilePicture(userId, profilePictureFile.path, userRole); + + res.status(200).json({ message: 'Profile picture updated successfully' }); + + } + + public getProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + const profilePictureUrl = await this.userService.getProfilePicture(userId); + if (!profilePictureUrl) { + const error = createBilingualError(404, ErrorMessages.NO_PROFILE_PICTURE); + throw new HttpException(error.status, error.message, error.messageAr); + } + res.status(200).json({ data: { url: profilePictureUrl }, message: 'Profile picture retrieved successfully' }); + } + + public deleteProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + await this.userService.deleteProfilePicture(userId); + res.status(200).json({ message: 'Profile picture deleted successfully' }); + } +} \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 1e00734..98b26bd 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -118,81 +118,5 @@ export class DoctorsRoute implements Routes { RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.doctorSetPassword) ); - - // Doctor Profile Picture Routes - this.router.patch( - `/doctors/profile-picture`, - /* - #swagger.tags = ['Doctors'] - #swagger.consumes = ['multipart/form-data'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.parameters['profilePicture'] = { - in: 'formData', - type: 'file', - required: true, - description: 'Profile picture file' - } - #swagger.responses[200] = { - description: 'Profile picture updated successfully', - schema: { - message: 'Profile picture updated successfully' - } - } - */ - AuthMiddleware, - RoleMiddleware(Role.DOCTOR), - upload.single('profilePicture'), - ValidationMiddleware(null, false, false, false, true), - errorWrapper(this.doctorsController.updateProfilePicture) - ); - this.router.get( - `/doctors/profile-picture`, - /* - #swagger.tags = ['Doctors'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Get profile picture successful', - schema: { - data: { - url: 'https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg' - }, - message: 'Profile picture retrieved successfully' - } - } - */ - AuthMiddleware, - errorWrapper(this.doctorsController.getProfilePicture) - ) - this.router.delete( - `/doctors/profile-picture`, - /* - #swagger.tags = ['Doctors'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Profile picture deleted successfully', - schema: { - message: 'Profile picture deleted successfully' - } - } - */ - AuthMiddleware, - RoleMiddleware(Role.DOCTOR), - errorWrapper(this.doctorsController.deleteProfilePicture) - ); } } \ No newline at end of file diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts new file mode 100644 index 0000000..65e9b2f --- /dev/null +++ b/src/routes/user.route.ts @@ -0,0 +1,95 @@ +import { UsersController } from "@/controllers/user.controller"; +import { Routes } from "@/interfaces"; +import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import upload from "@/middlewares/multer.middleware"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { errorWrapper } from "@/utils/errorWrapper"; +import { Router } from "express"; + +export class UsersRoute implements Routes { + public path = '/users' + public router = Router(); + public usersController = new UsersController(); + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + // User Profile Picture Routes + this.router.patch( + `${this.path}/profile-picture`, + /* + #swagger.path = '/users/profile-picture' + #swagger.tags = ['Users'] + #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['profilePicture'] = { + in: 'formData', + type: 'file', + required: true, + description: 'Profile picture file' + } + #swagger.responses[200] = { + description: 'Profile picture updated successfully', + schema: { + message: 'Profile picture updated successfully' + } + } + */ + AuthMiddleware, + upload.single('profilePicture'), + ValidationMiddleware(null, false, false, false, true), + errorWrapper(this.usersController.updateProfilePicture) + ); + this.router.get( + `${this.path}/profile-picture`, + /* + #swagger.path = '/users/profile-picture' + #swagger.tags = ['Users'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get profile picture successful', + schema: { + data: { + url: 'https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg' + }, + message: 'Profile picture retrieved successfully' + } + } + */ + AuthMiddleware, + errorWrapper(this.usersController.getProfilePicture) + ) + this.router.delete( + `${this.path}/profile-picture`, + /* + #swagger.path = '/users/profile-picture' + #swagger.tags = ['Users'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Profile picture deleted successfully', + schema: { + message: 'Profile picture deleted successfully' + } + } + */ + AuthMiddleware, + errorWrapper(this.usersController.deleteProfilePicture) + ); + } +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index ef92ba9..5be8d6f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,12 +6,14 @@ import { FabricRoute } from '@routes/fabric.route'; import { SuperAdminRoute } from './routes/superAdmin.route'; import { DoctorsRoute } from './routes/doctors.route'; import { ClinicRoute } from './routes/clinic.route'; +import { UsersRoute } from './routes/user.route'; ValidateEnv(); const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), - new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute() + new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), + new UsersRoute() ]); app.listen(); diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index ed627f0..313aa97 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -160,14 +160,4 @@ export class DoctorService { }); } - public async updateDoctorProfilePicture(doctorId: string, photoUrl: string, photoPublicId: string): Promise { - await prisma.user.update({ - where: { id: doctorId }, - data: { - photo_url: photoUrl, - photo_public_id: photoPublicId - } - }); - } - } \ No newline at end of file diff --git a/src/services/user.service.ts b/src/services/user.service.ts index f189c4c..8204232 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -10,7 +10,7 @@ const prisma = new PrismaClient(); @Service() export class UserService { - public async updateProfilePicture(userId: string, localFilePath: string): Promise<{ url: string; publicId: string }> { + public async updateProfilePicture(userId: string, localFilePath: string, userRole: string): Promise { const oldProfilePictureId = await prisma.user.findUnique({ where: { id: userId }, @@ -24,20 +24,24 @@ export class UserService { // Upload new profile picture to Cloudinary const uploadResult = await cloudinary.uploader.upload(localFilePath, { - folder: 'doctors/profile_pictures', + folder: `${userRole}S/profile_pictures`, overwrite: false, - public_id: `doctor_${userId}_profile_picture_${Date.now()}` + public_id: `${userRole}_${userId}_profile_picture_${Date.now()}` }); fs.unlinkSync(localFilePath); // Remove local file after upload - return { - url: uploadResult.secure_url, - publicId: uploadResult.public_id - } + // Update user record with new profile picture info + await prisma.user.update({ + where: { id: userId }, + data: { + photo_url: uploadResult.secure_url, + photo_public_id: uploadResult.public_id + } + }); } - public async getUserProfilePicture(userId: string): Promise { + public async getProfilePicture(userId: string): Promise { const user = await prisma.user.findUnique({ where: { id: userId }, select: { photo_url: true } diff --git a/src/swagger-output.json b/src/swagger-output.json index 79906e6..19fca27 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -31,6 +31,10 @@ { "name": "Clinics", "description": "Clinic endpoints" + }, + { + "name": "Users", + "description": "User account endpoints" } ], "schemes": [ @@ -2506,123 +2510,6 @@ } } }, - "/doctors/profile-picture": { - "patch": { - "tags": [ - "Doctors" - ], - "description": "", - "consumes": [ - "multipart/form-data" - ], - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - }, - { - "name": "profilePicture", - "in": "formData", - "type": "file", - "required": true, - "description": "Profile picture file" - } - ], - "responses": { - "200": { - "description": "Profile picture updated successfully", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Profile picture updated successfully" - } - }, - "xml": { - "name": "main" - } - } - } - } - }, - "get": { - "tags": [ - "Doctors" - ], - "description": "", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Get profile picture successful", - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "url": { - "type": "string", - "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" - } - } - }, - "message": { - "type": "string", - "example": "Profile picture retrieved successfully" - } - }, - "xml": { - "name": "main" - } - } - } - } - }, - "delete": { - "tags": [ - "Doctors" - ], - "description": "", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Profile picture deleted successfully", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Profile picture deleted successfully" - } - }, - "xml": { - "name": "main" - } - } - } - } - } - }, "/clinics": { "post": { "tags": [ @@ -2998,6 +2885,123 @@ } } } + }, + "/users/profile-picture": { + "patch": { + "tags": [ + "Users" + ], + "description": "", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "profilePicture", + "in": "formData", + "type": "file", + "required": true, + "description": "Profile picture file" + } + ], + "responses": { + "200": { + "description": "Profile picture updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture updated successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get profile picture successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" + } + } + }, + "message": { + "type": "string", + "example": "Profile picture retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Profile picture deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Profile picture deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index 848306b..cd4ca22 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,12 +14,14 @@ const doc = { { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, { name: 'Doctors', description: 'Doctor account endpoints' }, { name: 'Clinics', description: 'Clinic endpoints' }, + { name: 'Users', description: 'User account endpoints' }, ], - + }; const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts' , './src/routes/clinic.route.ts']; + './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' + , './src/routes/user.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From 215c5b49ca874cdf63689bd204e8071e144c5009 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 27 Jan 2026 23:22:12 +0200 Subject: [PATCH 079/210] Updated specialization to be IMMUNOLOGY only for all doctors --- src/constants/specializations.ts | 4 ++++ src/dtos/admins.dto.ts | 8 -------- src/dtos/doctors.dto.ts | 5 ----- src/services/admin.service.ts | 2 +- src/services/doctor.service.ts | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/constants/specializations.ts b/src/constants/specializations.ts index d7f4e89..27c7c2e 100644 --- a/src/constants/specializations.ts +++ b/src/constants/specializations.ts @@ -119,6 +119,10 @@ export const SPECIALIZATIONS = { en: 'Sports Medicine', ar: 'طب الرياضة', }, + IMMUNOLOGY: { + en: 'Immunology', + ar: 'امراض المناعة', + }, } as const; // Type for specialization keys diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 4c6eca0..e5c2030 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -21,14 +21,6 @@ export class AddDoctorFromAdminDto { @IsString() public gender: Gender; - - @IsString() - @IsNotEmpty() - @TransformSpecialization() // Converts EN/AR to key before validation - @IsValidSpecialization({ - message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' - }) - public specialization: string; } export class DoctorFromAdminResponseDto { diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index a0f5f97..4442413 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -27,11 +27,6 @@ export class DoctorSignupRequestDto { @IsString() public date_of_birth?: Date; - @TransformSpecialization() // Converts EN/AR to key before validation - @IsValidSpecialization({ - message: 'Specialization must be a valid specialization (English, Arabic, or key accepted)' - }) - public specialization: string; } export class DoctorLoginRequestDto { diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 50dd5d0..35e9c3d 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -59,7 +59,7 @@ export class AdminService { await prisma.doctor.create({ data: { id: createdUser.id, - specialization: doctorData.specialization, // This is now the KEY (e.g., "CARDIOLOGY") + specialization: "IMMUNOLOGY", // This is now the KEY (e.g., "IMMUNOLOGY") account_status: DoctorAccountStatus.APPROVED, } }); diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 313aa97..d7ec89c 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -60,7 +60,7 @@ export class DoctorService { await prisma.doctor.create({ data: { id: createdUser.id, - specialization: doctorData.specialization, + specialization: "IMMUNOLOGY", account_status: DoctorAccountStatus.PENDING, }, }); From f7ff523402eef480384d80fe609e372e8b01f59f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 28 Jan 2026 00:00:34 +0200 Subject: [PATCH 080/210] swagger edits --- src/routes/admin.route.ts | 1 - src/routes/doctors.route.ts | 1 - src/routes/superAdmin.route.ts | 1 - src/swagger-output.json | 21 +++------------------ 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 4c6a54a..c521664 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -31,7 +31,6 @@ export class AdminRoute implements Routes { $phone: '1234567890', $gender: 'MALE or FEMALE', $date_of_birth: '1990-01-01', - $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' } } #swagger.parameters['Authorization'] = { diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 98b26bd..536d461 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -35,7 +35,6 @@ export class DoctorsRoute implements Routes { $password: 'SecurePassword123', $gender: 'MALE or FEMALE', date_of_birth: '1990-01-01', - $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' } } #swagger.responses[201] = { diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index b606f70..250abb8 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -160,7 +160,6 @@ export class SuperAdminRoute implements Routes { $phone: '1234567890', $gender: 'MALE or FEMALE', $date_of_birth: '1990-01-01', - $specialization: 'CARDIOLOGY or امراض القلب or Cardiology' } } #swagger.parameters['Authorization'] = { diff --git a/src/swagger-output.json b/src/swagger-output.json index 19fca27..2684091 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1156,10 +1156,6 @@ "date_of_birth": { "type": "string", "example": "1990-01-01" - }, - "specialization": { - "type": "string", - "example": "CARDIOLOGY or امراض القلب or Cardiology" } }, "required": [ @@ -1167,8 +1163,7 @@ "name", "phone", "gender", - "date_of_birth", - "specialization" + "date_of_birth" ] } }, @@ -1964,10 +1959,6 @@ "date_of_birth": { "type": "string", "example": "1990-01-01" - }, - "specialization": { - "type": "string", - "example": "CARDIOLOGY or امراض القلب or Cardiology" } }, "required": [ @@ -1975,8 +1966,7 @@ "name", "phone", "gender", - "date_of_birth", - "specialization" + "date_of_birth" ] } }, @@ -2325,10 +2315,6 @@ "date_of_birth": { "type": "string", "example": "1990-01-01" - }, - "specialization": { - "type": "string", - "example": "CARDIOLOGY or امراض القلب or Cardiology" } }, "required": [ @@ -2336,8 +2322,7 @@ "name", "phone", "password", - "gender", - "specialization" + "gender" ] } } From bc4d807371ad5fea24487e1962e7cd948c683251 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 28 Jan 2026 16:50:42 +0200 Subject: [PATCH 081/210] Booking a new appointment routes are done 1- show offline clinics 2- show online doctors 3- get all doctors in a selected clinic 4- get available days 5- get available slots 6- book the appointment --- src/controllers/appointment.controller.ts | 55 +++++++++- src/dtos/appointments.dto.ts | 43 +++++--- src/interfaces/appointments.interface.ts | 7 +- src/routes/appointment.route.ts | 89 +++++++++++++++- src/services/appointment.service.ts | 80 ++++++++++++++- src/swagger-output.json | 119 ++++++++++++++++++++++ src/utils/errorMessages.ts | 37 +++++++ 7 files changed, 398 insertions(+), 32 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index c90f41f..535c8cf 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -4,6 +4,8 @@ import { HttpException } from "@/exceptions/HttpException"; import { catchAsync } from '@/utils/catchAsync'; import { AppointmentService } from "@/services/appointment.service" import Container from "typedi"; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; + export class AppointmentController { @@ -13,10 +15,14 @@ export class AppointmentController { const { doctorId } = req.params; const { clinicId } = req.query; + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + const availableDays = await this.appointmentService.getAvailableDays(doctorId, clinicId as string || null) res.status(200).json({ data: availableDays, - // message: 'Available days retrieved successfully', }); }); @@ -25,16 +31,61 @@ export class AppointmentController { const { doctorId } = req.params; const { date, clinicId } = req.query; + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!date) { + const error = createBilingualError(400, ErrorMessages.DATE_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // validate date format (YYYY-MM-DD) + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(date as string)) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const requestedDate = new Date(date as string); + if (isNaN(requestedDate.getTime())) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } + const availableSlots = await this.appointmentService.getAvailableSlots(doctorId, clinicId as string || null, date as string) res.status(200).json({ data: availableSlots, - // message: 'Available slots retrieved successfully', }); }); // book a new appointment + public bookAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const { doctorId, clinicId, scheduledTime } = req.body; + const scheduledDate = new Date(scheduledTime); + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + if (!scheduledTime) { + const error = createBilingualError(400, ErrorMessages.SCHEDULED_TIME_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (isNaN(scheduledDate.getTime())) { + const error = createBilingualError(400, ErrorMessages.INVALID_SCHEDULED_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.appointmentService.bookAppointment(patientId, doctorId, clinicId || null, scheduledDate); + res.status(201).json({ + message: 'Appointment booked successfully', + }); + }); } \ No newline at end of file diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index 865877d..6dddf85 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -1,28 +1,41 @@ -import { IsDateString, IsEnum, IsNotEmpty, IsString, Validate, ValidateIf } from "class-validator"; +import { IsString, IsNotEmpty, IsDateString, IsOptional, IsUUID } from 'class-validator'; -export class GetAvailableDaysDto { - @IsEnum(['ONLINE', 'OFFLINE']) + +export class BookAppointmentDto { + @IsUUID() @IsNotEmpty() - appointment_type: 'ONLINE' | 'OFFLINE'; + doctorId: string; - @ValidateIf(o => o.appointment_type == 'OFFLINE') - @IsString() + @IsUUID() + @IsOptional() + clinicId?: string; + + @IsDateString() @IsNotEmpty() - clinic_id?: string; + scheduledTime: string; +} + - @IsString() +export class GetAvailableDaysDto { + @IsUUID() @IsNotEmpty() - doctor_id?: string; + doctorId: string; + + @IsUUID() + @IsOptional() + clinicId?: string; } export class GetAvailableSlotsDto { - @IsDateString() + @IsUUID() @IsNotEmpty() - date: string; -} + doctorId: string; -export class CreateAppointmentDto { @IsDateString() @IsNotEmpty() - scheduled_time: string; -} + date: string; + + @IsUUID() + @IsOptional() + clinicId?: string; +} \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index f65fe29..09f7f67 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -7,7 +7,7 @@ export interface Appointment { doctor_id: string; clinic_id: string | null; scheduled_time: Date; - slot_duration: Date; + slot_duration: number; end_time: Date; is_online: boolean; is_completed: boolean; @@ -28,11 +28,6 @@ export interface AvailableDay { displayDate: string; } -// export interface AvailableSlot { -// start_time: string; -// end_time: string; -// } - export interface TimeSlot { start: string; end: string; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 5711f8c..d857ee2 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -3,6 +3,9 @@ import { Router } from "express"; import { ClinicController } from "@/controllers/clinic.controller"; import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import { BookAppointmentDto } from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -23,6 +26,12 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/online-doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.description = 'Get all available online doctors' #swagger.responses[200] = { description: 'Online doctors retrieved successfully', @@ -37,6 +46,7 @@ export class AppointmentRoute implements Routes { } } */ + AuthMiddleware, this.doctorController.getOnlineDoctors ); @@ -47,6 +57,12 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinics' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.description = 'Get all clinics available for booking appointments' #swagger.responses[200] = { description: 'Active clinics retrieved successfully', @@ -67,6 +83,7 @@ export class AppointmentRoute implements Routes { } } */ + AuthMiddleware, this.clinicController.getActiveClinics ); @@ -77,6 +94,12 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinic/{clinicId}/doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' #swagger.parameters['clinicId'] = { in: 'path', @@ -97,6 +120,7 @@ export class AppointmentRoute implements Routes { } } */ + AuthMiddleware, this.clinicController.getClinicDoctors ); @@ -107,6 +131,12 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/doctor/{doctorId}/available-days' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.description = 'Get all available days for a doctor that have at least one available slot' #swagger.parameters['doctorId'] = { in: 'path', @@ -150,6 +180,7 @@ export class AppointmentRoute implements Routes { description: 'Doctor not found or not available' } */ + AuthMiddleware, this.appointmentController.getAvailableDays ); @@ -160,6 +191,12 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/doctor/{doctorId}/available-slots' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } #swagger.description = 'Get all available time slots for a doctor on a specific date' #swagger.parameters['doctorId'] = { in: 'path', @@ -208,13 +245,57 @@ export class AppointmentRoute implements Routes { description: 'Bad request - missing required parameters or invalid date' } */ + AuthMiddleware, this.appointmentController.getAvailableSlots ); - // // book appointment - // this.router.post( - // `${this.path}/book-appointment` - // ) + // book appointment + this.router.post( + `${this.path}/book`, + /* + #swagger.path = '/appointments/book' + #swagger.method = 'post' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Book a new appointment with a doctor' + #swagger.security = [{ + bearerAuth: [] + }] + #swagger.parameters['body'] = { + in: 'body', + description: 'Appointment booking details', + required: true, + schema: { + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', + scheduledTime: '2026-02-03T09:00:00.000Z' + } + } + #swagger.responses[201] = { + description: 'Appointment booked successfully', + schema: { + message: 'Appointment booked successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid data or slot not available', + schema: { + message: 'Error message describing the issue' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + */ + AuthMiddleware, + ValidationMiddleware(BookAppointmentDto), + this.appointmentController.bookAppointment + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 3623fd4..cc27130 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -3,16 +3,21 @@ import { DayOfWeek } from '@prisma/client'; import { AvailableDay } from '@/interfaces'; import { Service } from 'typedi'; import { TimeSlot } from '@/interfaces'; -import { logger } from '@/utils/logger'; - +import { HttpException } from "@/exceptions/HttpException"; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; @Service() export class AppointmentService { - + public async getAvailableDays(doctorId: string, clinicId: string | null): Promise{ const daysAhead = 30 const availableDays: AvailableDay[] = []; - const isOnline = true; + const isOnline = await this.doctorIsOnline(doctorId); + + if (!isOnline && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } const schedules = await prisma.doctorSchedule.findMany({ where:{ @@ -78,7 +83,22 @@ export class AppointmentService { public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise[]>{ const requestedDate = new Date(date); const dayOfWeek = this.getDayOfWeek(requestedDate.getDay()); - const isOnline = true; + const isOnline = await this.doctorIsOnline(doctorId); + + if (!isOnline && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const requestedDateOnly = new Date(requestedDate); + requestedDateOnly.setHours(0, 0, 0, 0); + + if (requestedDateOnly < today) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_IN_PAST); + throw new HttpException(error.status, error.message, error.messageAr); + } const schedule = await prisma.doctorSchedule.findFirst({ where:{ @@ -148,6 +168,44 @@ export class AppointmentService { return availableSlots; } + public async bookAppointment(patientId: string, doctorId: string, clinicId: string | null, scheduledTime: Date): Promise{ + const isOnline = await this.doctorIsOnline(doctorId); + if (!isOnline && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const schedule = await prisma.doctorSchedule.findFirst({ + where:{ + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + is_active: true, + deleted_at: null, + day_of_week: this.getDayOfWeek(scheduledTime.getDay()), + }, + select:{ + slot_duration: true, + } + }); + + const endTime = new Date(scheduledTime.getTime() + schedule.slot_duration * 60000); + + const appointment = await prisma.appointment.create({ + data:{ + patient_id: patientId, + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + scheduled_time: scheduledTime, + slot_duration: schedule.slot_duration, + end_time: endTime, + is_online: isOnline, + estimated_time: schedule.slot_duration, + } + }); + + console.log('Appointment booked:', appointment); + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); @@ -222,4 +280,16 @@ export class AppointmentService { private doesSlotOverlap(slotStart: Date, slotEnd: Date, appointmentStart: Date, appointmentEnd: Date): boolean { return (slotStart < appointmentEnd && slotEnd > appointmentStart); } + + private async doctorIsOnline(doctorId: string): Promise { + const { availability_type } = await prisma.doctor.findUnique({ + where: { + id: doctorId, + }, + select: { + availability_type: true, + } + }); + return availability_type === 'ONLINE' || availability_type === 'BOTH'; + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index efb9ec6..5b1935d 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3009,6 +3009,15 @@ "Appointments" ], "description": "Get all available online doctors", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], "responses": { "200": { "description": "Online doctors retrieved successfully", @@ -3050,6 +3059,15 @@ "Appointments" ], "description": "Get all clinics available for booking appointments", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], "responses": { "200": { "description": "Active clinics retrieved successfully", @@ -3122,6 +3140,13 @@ "required": true, "type": "string", "description": "Clinic ID" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" } ], "responses": { @@ -3173,6 +3198,13 @@ "type": "string", "description": "Doctor ID" }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, { "name": "clinicId", "in": "query", @@ -3240,6 +3272,13 @@ "type": "string", "description": "Doctor ID" }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, { "name": "date", "in": "query", @@ -3293,6 +3332,86 @@ } } } + }, + "/appointments/book": { + "post": { + "tags": [ + "Appointments" + ], + "description": "Book a new appointment with a doctor", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Appointment booking details", + "required": true, + "schema": { + "type": "object", + "properties": { + "doctorId": { + "type": "string", + "example": "doctor-uuid" + }, + "clinicId": { + "type": "string", + "example": "clinic-uuid" + }, + "scheduledTime": { + "type": "string", + "example": "2026-02-03T09:00:00.000Z" + } + } + } + } + ], + "responses": { + "201": { + "description": "Appointment booked successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Appointment booked successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid data or slot not available", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 5c8cecb..d8c3c7c 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -122,10 +122,47 @@ export const ErrorMessages = { en: 'Clinic not found', ar: 'العيادة غير موجودة', }, + CLINIC_REQUIRED_FOR_OFFLINE: { + en: 'Clinic ID is required for offline appointments', + ar: 'معرف العيادة مطلوب للمواعيد غير المتصلة بالإنترنت', + }, UNAUTHORIZED_CLINIC_DELETION: { en: 'You are not authorized to delete this clinic', ar: 'ليس لديك صلاحية لحذف هذه العيادة', }, + // appointments + DOCTOR_ID_REQUIRED: { + en: 'Doctor ID is required', + ar: 'معرف الطبيب مطلوب', + }, + SCHEDULED_TIME_REQUIRED: { + en: 'Scheduled time is required', + ar: 'وقت الموعد مطلوب', + }, + INVALID_SCHEDULED_TIME: { + en: 'Invalid scheduled time format', + ar: 'تنسيق وقت الموعد غير صالح', + }, + DATE_REQUIRED: { + en: 'Date is required', + ar: 'التاريخ مطلوب', + }, + INVALID_DATE_FORMAT: { + en: 'Invalid date format. Please use YYYY-MM-DD', + ar: 'تنسيق التاريخ غير صالح. يرجى استخدام YYYY-MM-DD', + }, + NO_AVAILABLE_DAYS: { + en: 'No available days found for this doctor', + ar: 'لم يتم العثور على أيام متاحة لهذا الطبيب', + }, + SLOT_NOT_AVAILABLE: { + en: 'This time slot is not available', + ar: 'هذا الوقت غير متاح', + }, + APPOINTMENT_IN_PAST: { + en: 'Cannot book appointment in the past', + ar: 'لا يمكن حجز موعد في الماضي', + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From 4f795e655437a52c174c92aca954d3cfba8c3ec1 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 28 Jan 2026 19:22:12 +0200 Subject: [PATCH 082/210] get all patient's appointments --- src/controllers/appointment.controller.ts | 14 ++++ src/interfaces/appointments.interface.ts | 13 ++++ src/routes/appointment.route.ts | 50 ++++++++++++ src/services/appointment.service.ts | 42 +++++++++- src/swagger-output.json | 95 +++++++++++++++++++++++ src/utils/errorMessages.ts | 4 + 6 files changed, 217 insertions(+), 1 deletion(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 535c8cf..f05a585 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -88,4 +88,18 @@ export class AppointmentController { message: 'Appointment booked successfully', }); }); + + public getPatientAppointments = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + + if (!patientId) { + const error = createBilingualError(400, ErrorMessages.PATIENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appointments = await this.appointmentService.getPatientAppointments(patientId); + res.status(200).json({ + data: appointments, + }); + }); } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 09f7f67..24f5eca 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -22,6 +22,19 @@ export interface Appointment { doctor: User; } +export interface PatientAppointment { + id: string; + status: AppointmentStatus; + is_online: boolean; + slot_duration: number; + doctor_name: string; + appointment_date: string; + start_time: string; + end_time: string; + clinic_name: string | null; + clinic_address: string | null; +} + export interface AvailableDay { date: string; dayOfWeek: DayOfWeek; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index d857ee2..654bc84 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -296,6 +296,56 @@ export class AppointmentRoute implements Routes { ValidationMiddleware(BookAppointmentDto), this.appointmentController.bookAppointment ); + + this.router.get( + `${this.path}/patient/:patientId/appointments`, + /* + #swagger.path = '/appointments/patient/{patientId}/appointments' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Get all appointments for a specific patient' + #swagger.parameters['patientId'] = { + in: 'path', + description: 'Patient ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Patient appointments retrieved successfully', + schema: { + data: [ + { + id: 'appointment-uuid', + status: 'CONFIRMED', + is_online: true, + slot_duration: 20, + doctor_name: 'House', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:20', + clinic_name: 'Medical Park Clinic', + clinic_address: '123 Main Street, New Cairo' + } + ], + message: 'Patient appointments retrieved successfully' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + #swagger.responses[404] = { + description: 'Patient not found' + } + */ + AuthMiddleware, + this.appointmentController.getPatientAppointments + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index cc27130..6a7e6fd 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,10 +5,11 @@ import { Service } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { PatientAppointment } from '@/interfaces/appointments.interface'; @Service() export class AppointmentService { - + public async getAvailableDays(doctorId: string, clinicId: string | null): Promise{ const daysAhead = 30 const availableDays: AvailableDay[] = []; @@ -206,6 +207,45 @@ export class AppointmentService { console.log('Appointment booked:', appointment); } + public async getPatientAppointments(patientId: string): Promise { + const appointments = await prisma.appointment.findMany({ + where: { + patient_id: patientId, + }, + select: { + id: true, + scheduled_time: true, + status: true, + is_online: true, + slot_duration: true, + end_time: true, + doctor: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + } + }); + return appointments.map(appointment => ({ + id: appointment.id, + status: appointment.status, + is_online: appointment.is_online, + slot_duration: appointment.slot_duration, + doctor_name: appointment.doctor.name, + appointment_date: this.formatDate(appointment.scheduled_time), + start_time: this.formatTime(appointment.scheduled_time), + end_time: this.formatTime(appointment.end_time), + clinic_name: appointment.clinic ? appointment.clinic.name : null, + clinic_address: appointment.clinic ? appointment.clinic.address : null, + })); + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/swagger-output.json b/src/swagger-output.json index 5b1935d..b23a94c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3412,6 +3412,101 @@ } ] } + }, + "/appointments/patient/{patientId}/appointments": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all appointments for a specific patient", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "Patient ID" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Patient appointments retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "is_online": { + "type": "boolean", + "example": true + }, + "slot_duration": { + "type": "number", + "example": 20 + }, + "doctor_name": { + "type": "string", + "example": "House" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-03" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "09:20" + }, + "clinic_name": { + "type": "string", + "example": "Medical Park Clinic" + }, + "clinic_address": { + "type": "string", + "example": "123 Main Street, New Cairo" + } + } + } + }, + "message": { + "type": "string", + "example": "Patient appointments retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + }, + "404": { + "description": "Patient not found" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index d8c3c7c..c387b84 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -135,6 +135,10 @@ export const ErrorMessages = { en: 'Doctor ID is required', ar: 'معرف الطبيب مطلوب', }, + PATIENT_ID_REQUIRED: { + en: 'Patient ID is required', + ar: 'معرف المريض مطلوب', + }, SCHEDULED_TIME_REQUIRED: { en: 'Scheduled time is required', ar: 'وقت الموعد مطلوب', From c262a747471abfd9b7f012b9995011c140ab2f7d Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 28 Jan 2026 19:45:04 +0200 Subject: [PATCH 083/210] added multi lang response messages for the admin and auth controller --- src/controllers/admin.controller.ts | 24 ++++++--- src/controllers/auth.controller.ts | 59 +++++++++++++++++----- src/utils/responseMessages.ts | 77 +++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 18 deletions(-) create mode 100644 src/utils/responseMessages.ts diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 9622af3..0613d17 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -6,6 +6,7 @@ import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; import { RequestWithLanguage } from '@/middlewares/language.middleware'; import { formatSpecializationResponse } from '@/utils/specializationTransform'; import { SpecializationKey } from '@/constants/specializations'; +import { createMultiLangMessage, SuccessResponseMessages } from '@/utils/responseMessages'; export class AdminController { public adminService = Container.get(AdminService); @@ -26,9 +27,11 @@ export class AdminController { ...newDoctor, doctor: formattedNewDoctor, }; + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_CREATED); res.status(201).json({ data: doctorResponse, - message: 'Doctor added successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, }); }; @@ -48,10 +51,11 @@ export class AdminController { ), } : null, })); - + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTORS_RETRIEVED); res.status(200).json({ data: formattedDoctors, - message: 'Doctors retrieved successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, }); } @@ -73,10 +77,12 @@ export class AdminController { ), } : null, }; + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_RETRIEVED); res.status(200).json({ data: formattedDoctor, - message: 'Doctor retrieved successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, }); } @@ -95,9 +101,11 @@ export class AdminController { ), } : null, })); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.UNVERIFIED_DOCTORS_RETRIEVED); res.status(200).json({ data: formattedDoctors, - message: 'Unverified doctors retrieved successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, }); } @@ -105,9 +113,11 @@ export class AdminController { const doctorId = req.params.id; const { isVerified } = req.body; - await this.adminService.updateDoctorVerificationStatus(doctorId, isVerified); + await this.adminService.updateDoctorVerificationStatus(doctorId, isVerified); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_VERIFICATION_STATUS_UPDATED); res.status(200).json({ - message: 'Doctor verification status updated successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, }); } } \ No newline at end of file diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index dda901a..953414c 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -7,6 +7,7 @@ import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } import { catchAsync } from '@/utils/catchAsync'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { HttpException } from '@/exceptions/HttpException'; +import { createMultiLangMessage, SuccessResponseMessages } from '@/utils/responseMessages'; export class AuthController { public auth = Container.get(AuthService); @@ -18,8 +19,12 @@ export class AuthController { res.setHeader('Set-Cookie', cookies); await this.auth.sendEmailOtp(userData.email); - - res.status(201).json({ data: createdUserData, message: 'Signed Up Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.SIGNED_UP_SUCCESSFULLY); + res.status(201).json({ + data: createdUserData, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public logIn = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { @@ -27,7 +32,12 @@ export class AuthController { const { cookies, findUser } = await this.auth.login(userData); res.setHeader('Set-Cookie', cookies); - res.status(200).json({ data: findUser, message: 'Logged In Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.LOGGED_IN_SUCCESSFULLY); + res.status(200).json({ + data: findUser, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public logOut = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { @@ -38,8 +48,11 @@ export class AuthController { 'Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax', 'RefreshToken=; HttpOnly; Max-Age=0; Path=/; SameSite=Lax' ]); - - res.status(200).json({ message: 'Logged Out Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.LOGGED_OUT_SUCCESSFULLY); + res.status(200).json({ + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public refresh = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { @@ -47,6 +60,7 @@ export class AuthController { const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); res.setHeader('Set-Cookie', cookies); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.TOKEN_REFRESHED_SUCCESSFULLY); res.status(200).json({ data: { user, @@ -55,7 +69,8 @@ export class AuthController { expiresAt: new Date(Date.now() + accessToken.expiresIn * 1000) } }, - message: 'Token Refreshed Successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr }); }); @@ -64,7 +79,12 @@ export class AuthController { const profileData: CompleteUserProfileDto = req.body; const updatedUserData: User = await this.auth.completeProfile(userData, profileData); - res.status(200).json({ data: updatedUserData, message: 'Profile Completed Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PROFILE_COMPLETED_SUCCESSFULLY); + res.status(200).json({ + data: updatedUserData, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public verifyOTP = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { @@ -75,7 +95,12 @@ export class AuthController { throw new HttpException(error.status, error.message, error.messageAr); } const isSuccessful = await this.auth.verifyEmailOtp(email, otp); - res.status(200).json({ data: isSuccessful, message: 'OTP Verified Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.OTP_VERIFIED_SUCCESSFULLY); + res.status(200).json({ + data: isSuccessful, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public forgetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { @@ -85,20 +110,32 @@ export class AuthController { throw new HttpException(error.status, error.message, error.messageAr); } await this.auth.sendPasswordResetEmail(email); - res.status(200).json({ message: 'Password Reset Email Sent Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_RESET_EMAIL_SENT_SUCCESSFULLY); + res.status(200).json({ + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public resetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const { token, newPassword }: ResetPasswordDto = req.body; await this.auth.resetPassword(token, newPassword); - res.status(200).json({ message: 'Password Reset Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_RESET_SUCCESSFULLY); + res.status(200).json({ + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); public resendOTP = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const email = await this.auth.getUserEmail(req) await this.auth.sendEmailOtp(email); - res.status(200).json({ message: 'OTP Resent Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.OTP_RESENT_SUCCESSFULLY); + res.status(200).json({ + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); }); } diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts new file mode 100644 index 0000000..3c21481 --- /dev/null +++ b/src/utils/responseMessages.ts @@ -0,0 +1,77 @@ +export const SuccessResponseMessages = { + // Success messages for Auth + + SIGNED_UP_SUCCESSFULLY: { + message_en: "Signed up successfully.", + message_ar: "تم انشاء حساب جديد بنجاح.", + }, + LOGGED_IN_SUCCESSFULLY: { + message_en: "Logged in successfully.", + message_ar: "تم تسجيل الدخول بنجاح.", + }, + LOGGED_OUT_SUCCESSFULLY: { + message_en: "Logged out successfully.", + message_ar: "تم تسجيل الخروج بنجاح.", + }, + TOKEN_REFRESHED_SUCCESSFULLY: { + message_en: "Token refreshed successfully.", + message_ar: "تم تحديث رمز الدخول بنجاح.", + }, + PROFILE_COMPLETED_SUCCESSFULLY: { + message_en: "Profile completed successfully.", + message_ar: "تم إكمال الملف الشخصي بنجاح.", + }, + OTP_VERIFIED_SUCCESSFULLY: { + message_en: "OTP verified successfully.", + message_ar: "تم التحقق من رمز التحقق بنجاح.", + }, + PASSWORD_RESET_EMAIL_SENT_SUCCESSFULLY: { + message_en: "Password reset email sent successfully.", + message_ar: "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح.", + }, + PASSWORD_RESET_SUCCESSFULLY: { + message_en: "Password reset successfully.", + message_ar: "تم إعادة تعيين كلمة المرور بنجاح.", + }, + OTP_RESENT_SUCCESSFULLY: { + message_en: "OTP resent successfully.", + message_ar: "تم إعادة إرسال رمز التحقق بنجاح.", + }, + + + // Success messages for Doctors by Admin + + DOCTOR_CREATED: { + message_en: "Doctor created successfully.", + message_ar: "تم إنشاء حساب الطبيب بنجاح.", + }, + DOCTOR_RETRIEVED: { + message_en: "Doctor retrieved successfully.", + message_ar: "تم استرجاع بيانات الطبيب بنجاح.", + }, + DOCTORS_RETRIEVED: { + message_en: "Doctors retrieved successfully.", + message_ar: "تم استرجاع بيانات الأطباء بنجاح.", + }, + UNVERIFIED_DOCTORS_RETRIEVED: { + message_en: "Unverified doctors retrieved successfully.", + message_ar: "تم استرجاع بيانات الأطباء غير المعتمدين بنجاح.", + }, + DOCTOR_VERIFICATION_STATUS_UPDATED: { + message_en: "Doctor verification status updated successfully.", + message_ar: "تم تحديث حالة اعتماد الطبيب بنجاح.", + }, + +} + +interface MultiLangMessageObj { + message_en: string; + message_ar: string; +} + +export const createMultiLangMessage = (multiLangMessageObj: MultiLangMessageObj) => { + return { + messageEn: multiLangMessageObj.message_en, + messageAr: multiLangMessageObj.message_ar, + }; +} \ No newline at end of file From af44f45f1c29c7dd679aae1c1f4a2935edf1183a Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 28 Jan 2026 20:10:38 +0200 Subject: [PATCH 084/210] Finished all the current controllers response message to be multi lang --- src/controllers/clinic.controller.ts | 24 +++++--- src/controllers/doctor.controller.ts | 16 ++++-- src/controllers/googleAuth.controller.ts | 7 ++- src/controllers/superAdmin.controller.ts | 13 ++++- src/controllers/user.controller.ts | 12 ++-- src/utils/responseMessages.ts | 73 +++++++++++++++++++++++- 6 files changed, 119 insertions(+), 26 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 42aeaca..c42b434 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -3,6 +3,7 @@ import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser } from "@/interfaces"; import { ClinicService } from "@/services/clinic.service"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; import Container from "typedi"; @@ -22,7 +23,9 @@ export class ClinicController { this.clinicService.linkDoctorToClinic(req.user.id, createdClinic, clinicData.fees); - res.status(201).json({ message: 'Clinic created successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_CREATED_SUCCESSFULLY); + + res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } public getClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { @@ -33,8 +36,8 @@ export class ClinicController { const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } - - res.status(200).json({ message: 'Clinic retrieved successfully', data: clinic }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_RETRIEVED); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr, data: clinic }); } public updateClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { @@ -47,8 +50,8 @@ export class ClinicController { const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } - - res.status(200).json({ message: 'Clinic updated successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_UPDATED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } public deleteClinicById = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { @@ -61,13 +64,18 @@ export class ClinicController { throw new HttpException(error.status, error.message, error.messageAr); } await this.clinicService.deleteClinic(clinicId); - - res.status(200).json({ message: 'Clinic deleted successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_DELETED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } public getDoctorClinics = async (req: RequestWithUser, res: Response, next: NextFunction) => { const doctorId = req.user?.id; const clinics = await this.clinicService.getDoctorClinics(doctorId); - res.status(200).json({ data: clinics, message: 'Doctor clinics retrieved successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_DOCTORS_RETRIEVED); + res.status(200).json({ + data: clinics, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); } } diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index fe1d49e..0d69c4c 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,10 +1,9 @@ import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; -import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser } from "@/interfaces"; import { DoctorService } from "@/services/doctor.service"; import { UserService } from "@/services/user.service"; -import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; import { Container } from "typedi"; @@ -16,7 +15,8 @@ export class DoctorController { public doctorSignup = async (req: Request, res: Response, next: NextFunction) => { const doctorData: DoctorSignupRequestDto = req.body; await this.doctorService.signup(doctorData); - res.status(201).json({ message: 'Doctor signed up successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_CREATED_WAITING_VERIFICATION); + res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }; public doctorLogin = async (req: Request, res: Response, next: NextFunction) => { @@ -29,7 +29,12 @@ export class DoctorController { } else if (typeof loginResult === 'object') { const { cookies, doctorAccountData } = loginResult; res.setHeader('Set-Cookie', cookies); - res.status(200).json({ data: doctorAccountData, message: 'Doctor logged in successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_RETRIEVED); + res.status(200).json({ + data: doctorAccountData, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); } } @@ -37,7 +42,8 @@ export class DoctorController { const doctorId = req.user?.id; const { password }: DoctorSetPasswordRequestDto = req.body; await this.doctorService.setPassword(doctorId, password); - res.status(200).json({ message: 'Password set successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_SET_SUCCESSFULLY_BY_DOCTOR); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } } \ No newline at end of file diff --git a/src/controllers/googleAuth.controller.ts b/src/controllers/googleAuth.controller.ts index c184786..0532741 100644 --- a/src/controllers/googleAuth.controller.ts +++ b/src/controllers/googleAuth.controller.ts @@ -6,6 +6,7 @@ import { User, UserLoginData } from "@/interfaces/users.interface"; import { RequestWithUser } from "@/interfaces"; import { GoogleAuthService } from "@/services/googleAuth.service"; import { catchAsync } from "@/utils/catchAsync"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; export class GoogleAuthController { public authService = Container.get(AuthService); @@ -52,11 +53,13 @@ export class GoogleAuthController { public updatePhoneNumber = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const phone: string = req.body.phone; await this.googleAuthService.updatePhoneNumber(req.user.id, phone); - res.status(200).json({ message: 'Phone Number Updated Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PHONE_NUMBER_UPDATED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); public getGoogleUserData = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const googleUserData: UserLoginData = await this.googleAuthService.getGoogleUserData(req.user.id); - res.status(200).json({ data: googleUserData, message: 'Google User Data Retrieved Successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.GOOGLE_USER_DATA_RETRIEVED); + res.status(200).json({ data: googleUserData, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); } \ No newline at end of file diff --git a/src/controllers/superAdmin.controller.ts b/src/controllers/superAdmin.controller.ts index 53922da..ad899ae 100644 --- a/src/controllers/superAdmin.controller.ts +++ b/src/controllers/superAdmin.controller.ts @@ -1,5 +1,6 @@ import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; import { SuperAdminService } from "@/services/superAdmin.service"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Response, Request } from "express"; import Container from "typedi"; @@ -10,26 +11,32 @@ export class SuperAdminController { public addAdmin = async (req: Request, res: Response, next: NextFunction): Promise => { const adminData: AddAdminFromSuperAdminDto = req.body; const newAdmin = await this.superAdminService.addAdmin(adminData); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ADMIN_ADDED_SUCCESSFULLY); res.status(201).json({ data: newAdmin, - message: 'Admin added successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr }); } public getAllAdmins = async (req: Request, res: Response, next: NextFunction): Promise => { const admins = await this.superAdminService.getAllAdmins(); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ADMINS_RETRIEVED_SUCCESSFULLY); res.status(200).json({ data: admins, - message: 'Admins retrieved successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr }); } public getAdminById = async (req: Request, res: Response, next: NextFunction): Promise => { const adminId: string = req.params.id; const admin = await this.superAdminService.getAdminById(adminId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ADMIN_RETRIEVED_SUCCESSFULLY); res.status(200).json({ data: admin, - message: 'Admin retrieved successfully' + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr }); } } \ No newline at end of file diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 86f0fc0..6bdcb6b 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -2,6 +2,7 @@ import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser } from "@/interfaces"; import { UserService } from "@/services/user.service"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; import { Container } from "typedi"; @@ -19,9 +20,8 @@ export class UsersController { throw new HttpException(error.status, error.message, error.messageAr); } await this.userService.updateProfilePicture(userId, profilePictureFile.path, userRole); - - res.status(200).json({ message: 'Profile picture updated successfully' }); - + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PROFILE_PICTURE_UPDATED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } public getProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { @@ -31,12 +31,14 @@ export class UsersController { const error = createBilingualError(404, ErrorMessages.NO_PROFILE_PICTURE); throw new HttpException(error.status, error.message, error.messageAr); } - res.status(200).json({ data: { url: profilePictureUrl }, message: 'Profile picture retrieved successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PROFILE_PICTURE_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ data: { url: profilePictureUrl }, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } public deleteProfilePicture = async (req: RequestWithUser, res: Response, next: NextFunction) => { const userId = req.user?.id; await this.userService.deleteProfilePicture(userId); - res.status(200).json({ message: 'Profile picture deleted successfully' }); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PROFILE_PICTURE_DELETED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 3c21481..32c6217 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -1,6 +1,5 @@ export const SuccessResponseMessages = { // Success messages for Auth - SIGNED_UP_SUCCESSFULLY: { message_en: "Signed up successfully.", message_ar: "تم انشاء حساب جديد بنجاح.", @@ -38,9 +37,7 @@ export const SuccessResponseMessages = { message_ar: "تم إعادة إرسال رمز التحقق بنجاح.", }, - // Success messages for Doctors by Admin - DOCTOR_CREATED: { message_en: "Doctor created successfully.", message_ar: "تم إنشاء حساب الطبيب بنجاح.", @@ -62,6 +59,76 @@ export const SuccessResponseMessages = { message_ar: "تم تحديث حالة اعتماد الطبيب بنجاح.", }, + // Success messages for Clinics + CLINIC_CREATED_SUCCESSFULLY: { + message_en: "Clinic created successfully.", + message_ar: "تم إنشاء العيادة بنجاح.", + }, + CLINIC_RETRIEVED: { + message_en: "Clinic data retrieved successfully.", + message_ar: "تم استرجاع بيانات العيادة بنجاح.", + }, + CLINIC_UPDATED_SUCCESSFULLY: { + message_en: "Clinic data updated successfully.", + message_ar: "تم تحديث بيانات العيادة بنجاح.", + }, + CLINIC_DELETED_SUCCESSFULLY: { + message_en: "Clinic deleted successfully.", + message_ar: "تم حذف العيادة بنجاح.", + }, + CLINIC_DOCTORS_RETRIEVED: { + message_en: "Clinic's doctors retrieved successfully.", + message_ar: "تم استرجاع أطباء العيادة بنجاح.", + }, + + // Success messages for Doctors + DOCTOR_CREATED_WAITING_VERIFICATION: { + message_en: "Doctor account created successfully. Please wait for verification.", + message_ar: "تم إنشاء حساب الطبيب بنجاح. يرجى الانتظار للموافقة عليه.", + }, + PASSWORD_SET_SUCCESSFULLY_BY_DOCTOR: { + message_en: "Password set successfully.", + message_ar: "تم تعيين كلمة المرور بنجاح.", + }, + + // Success messages for Google Auth + PHONE_NUMBER_UPDATED_SUCCESSFULLY: { + message_en: "Phone number updated successfully.", + message_ar: "تم تحديث رقم الهاتف بنجاح.", + }, + GOOGLE_USER_DATA_RETRIEVED: { + message_en: "Google user data retrieved successfully.", + message_ar: "تم استرجاع بيانات مستخدم جوجل بنجاح.", + }, + + // Success messages for Super Admin + ADMIN_ADDED_SUCCESSFULLY: { + message_en: "Admin added successfully.", + message_ar: "تم إضافة المسؤول بنجاح.", + }, + ADMINS_RETRIEVED_SUCCESSFULLY: { + message_en: "Admins retrieved successfully.", + message_ar: "تم استرجاع بيانات المسؤولين بنجاح.", + }, + ADMIN_RETRIEVED_SUCCESSFULLY: { + message_en: "Admin retrieved successfully.", + message_ar: "تم استرجاع بيانات المسؤول بنجاح.", + }, + + // Success messages for User + PROFILE_PICTURE_UPDATED_SUCCESSFULLY: { + message_en: "Profile picture updated successfully.", + message_ar: "تم تحديث صورة الملف الشخصي بنجاح.", + }, + PROFILE_PICTURE_RETRIEVED_SUCCESSFULLY: { + message_en: "Profile picture retrieved successfully.", + message_ar: "تم استرجاع صورة الملف الشخصي بنجاح.", + }, + PROFILE_PICTURE_DELETED_SUCCESSFULLY: { + message_en: "Profile picture deleted successfully.", + message_ar: "تم حذف صورة الملف الشخصي بنجاح.", + }, + } interface MultiLangMessageObj { From 4924c0f565a32005f8d2b510b5706f897defff2f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 28 Jan 2026 20:35:01 +0200 Subject: [PATCH 085/210] updated swagger for multi lang response --- src/routes/admin.route.ts | 15 ++- src/routes/auth.route.ts | 29 +++-- src/routes/clinic.route.ts | 15 ++- src/routes/doctors.route.ts | 10 +- src/routes/superAdmin.route.ts | 18 ++- src/routes/user.route.ts | 9 +- src/swagger-output.json | 200 +++++++++++++++++++++++++++------ 7 files changed, 228 insertions(+), 68 deletions(-) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index c521664..f95b724 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -51,7 +51,8 @@ export class AdminRoute implements Routes { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status: 'PENDING' }, photoUrl: null }, - message: 'Doctor added successfully' + messageEn: "Doctor account created successfully.", + messageAr: ".تم إنشاء حساب الطبيب بنجاح" } } */ @@ -84,7 +85,8 @@ export class AdminRoute implements Routes { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' }, photoUrl: null }], - message: 'Doctors retrieved successfully' + messageEn: 'Doctors retrieved successfully', + messageAr: "تم استرجاع بيانات الأطباء بنجاح." } } */ @@ -116,7 +118,8 @@ export class AdminRoute implements Routes { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, date_of_birth: '1990-01-01', photoUrl: null, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' } }], - message: 'Unverified doctors retrieved successfully' + messageEn: 'Unverified doctors retrieved successfully', + messageAr: "تم استرجاع بيانات الأطباء غير المعتمدين بنجاح." } } */ @@ -153,7 +156,8 @@ export class AdminRoute implements Routes { #swagger.responses[200] = { description: 'Doctor verification status updated successfully', schema: { - message: 'Doctor verification status updated successfully' + messageEn: 'Doctor verification status updated successfully', + messageAr: "تم تحديث حالة اعتماد الطبيب بنجاح." } } */ @@ -190,7 +194,8 @@ export class AdminRoute implements Routes { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status: 'APPROVED' }, photoUrl: null }, - message: 'Doctor retrieved successfully' + messageEn: 'Doctor retrieved successfully', + messageAr: "تم استرجاع بيانات الطبيب بنجاح." } } */ diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index c35d130..aa138ba 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -49,7 +49,8 @@ export class AuthRoute implements Routes { role: 'PATIENT', photoUrl: null }, - message: 'Signed Up Successfully' + messageEn: 'Signed Up Successfully', + messageAr: "تم انشاء الحساب بنجاح" } } */ @@ -75,7 +76,8 @@ export class AuthRoute implements Routes { description: 'Login successful', schema: { data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT' , doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, - message: 'Logged In Successfully' + messageEn: 'Logged In Successfully', + messageAr: "تم تسجيل الدخول بنجاح" } } */ @@ -95,7 +97,7 @@ export class AuthRoute implements Routes { } #swagger.responses[200] = { description: 'Logout successful', - schema: { message: 'Logged Out Successfully' } + schema: { messageEn: 'Logged Out Successfully', messageAr: "تم تسجيل الخروج بنجاح" } } */ AuthMiddleware, @@ -116,7 +118,8 @@ export class AuthRoute implements Routes { description: 'Token refreshed successfully', schema: { data: { user: {}, accessToken: { expiresIn: 3600, expiresAt: '2025-12-12T12:00:00.000Z' } }, - message: 'Token Refreshed Successfully' + messageEn: 'Token Refreshed Successfully', + messageAr: "تم تحديث الرمز بنجاح" } } */ @@ -147,7 +150,8 @@ export class AuthRoute implements Routes { description: 'Profile completed successfully', schema: { data: { id: 1, hasCompletedProfile: true }, - message: 'Profile Completed Successfully' + messageEn: 'Profile Completed Successfully', + messageAr: "تم إكمال الملف الشخصي بنجاح" } } */ @@ -184,7 +188,8 @@ export class AuthRoute implements Routes { description: 'OTP verified successfully', schema: { data: true, - message: 'OTP Verified Successfully' + messageEn: 'OTP Verified Successfully', + messageAr: "تم التحقق من رمز التحقق بنجاح" } } */ @@ -204,7 +209,7 @@ export class AuthRoute implements Routes { } #swagger.responses[200] = { description: 'Password reset email sent', - schema: { message: 'Password Reset Email Sent Successfully' } + schema: { messageEn: 'Password Reset Email Sent Successfully', messageAr: "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح" } } */ this.auth.forgetPassword, @@ -225,7 +230,7 @@ export class AuthRoute implements Routes { } #swagger.responses[200] = { description: 'Password reset successfully', - schema: { message: 'Password Reset Successfully' } + schema: { messageEn: 'Password Reset Successfully', messageAr: "تم إعادة تعيين كلمة المرور بنجاح" } } */ ValidationMiddleware(ResetPasswordDto), @@ -244,7 +249,7 @@ export class AuthRoute implements Routes { } #swagger.responses[200] = { description: 'OTP resent successfully', - schema: { message: 'OTP Resent Successfully' } + schema: { messageEn: 'OTP Resent Successfully', messageAr: "تم إعادة إرسال رمز التحقق بنجاح" } } */ AuthMiddleware, @@ -293,7 +298,8 @@ export class AuthRoute implements Routes { description: 'Phone number updated successfully', schema: { data: { phone: '1234567890' }, - message: 'Phone number updated successfully' + messageEn: 'Phone number updated successfully', + messageAr: "تم تحديث رقم الهاتف بنجاح" } } */ @@ -324,7 +330,8 @@ export class AuthRoute implements Routes { description: 'User data retrieved successfully', schema: { data: { email: 'user@example.com', name: 'John Doe', username: 'johndoe', phone: '1234567890', gender: 'MALE' , date_of_birth: '1990-01-01', isVerified: false, hasCompletedProfile: false }, - message: 'User data retrieved successfully' + messageEn: 'User data retrieved successfully', + messageAr: "تم استرجاع بيانات المستخدم بنجاح" } } */ diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index 2d10e5e..7c632b5 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -45,7 +45,8 @@ export class ClinicRoute implements Routes { #swagger.responses[201] = { description: 'Clinic created successfully', schema: { - message: 'Clinic created successfully' + messageEn: 'Clinic created successfully', + messageAr: "تم إنشاء العيادة بنجاح" } } */ @@ -88,7 +89,8 @@ export class ClinicRoute implements Routes { canPayOnline: true, created_at: '2024-01-01T00:00:00.000Z' }, - message: 'Clinic retrieved successfully' + messageEn: 'Clinic retrieved successfully', + messageAr: "تم استرجاع بيانات العيادة بنجاح" } } */ @@ -132,7 +134,8 @@ export class ClinicRoute implements Routes { #swagger.responses[200] = { description: 'Clinic updated successfully', schema: { - message: 'Clinic updated successfully' + messageEn: 'Clinic updated successfully', + messageAr: "تم تحديث العيادة بنجاح" } } */ @@ -163,7 +166,8 @@ export class ClinicRoute implements Routes { #swagger.responses[200] = { description: 'Clinic deleted successfully', schema: { - message: 'Clinic deleted successfully' + messageEn: 'Clinic deleted successfully', + messageAr: "تم حذف العيادة بنجاح" } } */ @@ -202,7 +206,8 @@ export class ClinicRoute implements Routes { fees: 100 } ], - message: "Doctor's clinics retrieved successfully" + messageEn: "Doctor's clinics retrieved successfully", + messageAr: "تم استرجاع عيادات الطبيب بنجاح" } } */ diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 536d461..51cd377 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -5,7 +5,6 @@ import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; import { errorWrapper } from "@/utils/errorWrapper"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; -import upload from "@/middlewares/multer.middleware"; import { Role } from "@prisma/client"; @@ -40,7 +39,8 @@ export class DoctorsRoute implements Routes { #swagger.responses[201] = { description: 'Doctor signup successful', schema: { - message: 'Doctor registered successfully' + messageEn: 'Doctor registered successfully', + messageAr: "تم تسجيل الطبيب بنجاح" } } */ @@ -78,7 +78,8 @@ export class DoctorsRoute implements Routes { account_status: 'APPROVED' } }, - message: 'Doctor logged in successfully' + messageEn: 'Doctor logged in successfully', + messageAr: "تم تسجيل دخول الطبيب بنجاح" } } */ @@ -108,7 +109,8 @@ export class DoctorsRoute implements Routes { #swagger.responses[200] = { description: 'Password set successfully', schema: { - message: 'Password updated successfully' + messageEn: 'Password updated successfully', + messageAr: "تم تحديث كلمة المرور بنجاح" } } */ diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 250abb8..2f0f808 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -60,7 +60,8 @@ export class SuperAdminRoute implements Routes { isVerified: true, hasCompletedProfile: true }, - message: 'Admin added successfully' + messageEn: 'Admin added successfully', + messageAr: "تم إضافة المسؤول بنجاح" } } */ @@ -96,7 +97,8 @@ export class SuperAdminRoute implements Routes { isVerified: true, hasCompletedProfile: true }], - message: 'Admins retrieved successfully' + messageEn: 'Admins retrieved successfully', + messageAr: "تم استرجاع المسؤولين بنجاح" } } */ @@ -136,7 +138,8 @@ export class SuperAdminRoute implements Routes { isVerified: true, hasCompletedProfile: true }, - message: 'Admin retrieved successfully' + messageEn: 'Admin retrieved successfully', + messageAr: "تم استرجاع المسؤول بنجاح" } } */ @@ -180,7 +183,8 @@ export class SuperAdminRoute implements Routes { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null }, photoUrl: null }, - message: 'Doctor added successfully' + messageEn: 'Doctor added successfully', + messageAr: "تم إضافة الطبيب بنجاح" } } */ @@ -213,7 +217,8 @@ export class SuperAdminRoute implements Routes { data: [{ id: '1', email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }], - message: 'Doctors retrieved successfully' + messageEn: 'Doctors retrieved successfully', + messageAr: "تم استرجاع الأطباء بنجاح" } } */ @@ -251,7 +256,8 @@ export class SuperAdminRoute implements Routes { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }, - message: 'Doctor retrieved successfully' + messageEn: 'Doctor retrieved successfully', + messageAr: "تم استرجاع الطبيب بنجاح" } } */ diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 65e9b2f..83fa798 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -37,7 +37,8 @@ export class UsersRoute implements Routes { #swagger.responses[200] = { description: 'Profile picture updated successfully', schema: { - message: 'Profile picture updated successfully' + messageEn: 'Profile picture updated successfully', + messageAr: "تم تحديث صورة الملف الشخصي بنجاح" } } */ @@ -63,7 +64,8 @@ export class UsersRoute implements Routes { data: { url: 'https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg' }, - message: 'Profile picture retrieved successfully' + messageEn: 'Profile picture retrieved successfully', + messageAr: "تم استرجاع صورة الملف الشخصي بنجاح" } } */ @@ -84,7 +86,8 @@ export class UsersRoute implements Routes { #swagger.responses[200] = { description: 'Profile picture deleted successfully', schema: { - message: 'Profile picture deleted successfully' + messageEn: 'Profile picture deleted successfully', + messageAr: "تم حذف صورة الملف الشخصي بنجاح" } } */ diff --git a/src/swagger-output.json b/src/swagger-output.json index 2684091..750c022 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -124,9 +124,13 @@ "photoUrl": {} } }, - "message": { + "messageEn": { "type": "string", "example": "Signed Up Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم انشاء الحساب بنجاح" } }, "xml": { @@ -212,9 +216,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Logged In Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تسجيل الدخول بنجاح" } }, "xml": { @@ -246,9 +254,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Logged Out Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تسجيل الخروج بنجاح" } }, "xml": { @@ -302,9 +314,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Token Refreshed Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث الرمز بنجاح" } }, "xml": { @@ -372,9 +388,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Profile Completed Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إكمال الملف الشخصي بنجاح" } }, "xml": { @@ -428,9 +448,13 @@ "type": "boolean", "example": true }, - "message": { + "messageEn": { "type": "string", "example": "OTP Verified Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم التحقق من رمز التحقق بنجاح" } }, "xml": { @@ -473,9 +497,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Password Reset Email Sent Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح" } }, "xml": { @@ -523,9 +551,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Password Reset Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إعادة تعيين كلمة المرور بنجاح" } }, "xml": { @@ -557,9 +589,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "OTP Resent Successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إعادة إرسال رمز التحقق بنجاح" } }, "xml": { @@ -644,9 +680,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Phone number updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث رقم الهاتف بنجاح" } }, "xml": { @@ -715,9 +755,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "User data retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات المستخدم بنجاح" } }, "xml": { @@ -1249,9 +1293,13 @@ "photoUrl": {} } }, - "message": { + "messageEn": { "type": "string", - "example": "Doctor added successfully" + "example": "Doctor account created successfully." + }, + "messageAr": { + "type": "string", + "example": ".تم إنشاء حساب الطبيب بنجاح" } }, "xml": { @@ -1356,9 +1404,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Doctors retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الأطباء بنجاح." } }, "xml": { @@ -1461,9 +1513,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Unverified doctors retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الأطباء غير المعتمدين بنجاح." } }, "xml": { @@ -1520,9 +1576,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Doctor verification status updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث حالة اعتماد الطبيب بنجاح." } }, "xml": { @@ -1629,9 +1689,13 @@ "photoUrl": {} } }, - "message": { + "messageEn": { "type": "string", "example": "Doctor retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الطبيب بنجاح." } }, "xml": { @@ -1748,9 +1812,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Admin added successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إضافة المسؤول بنجاح" } }, "xml": { @@ -1829,9 +1897,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Admins retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع المسؤولين بنجاح" } }, "xml": { @@ -1912,9 +1984,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Admin retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع المسؤول بنجاح" } }, "xml": { @@ -2048,9 +2124,13 @@ "photoUrl": {} } }, - "message": { + "messageEn": { "type": "string", "example": "Doctor added successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إضافة الطبيب بنجاح" } }, "xml": { @@ -2155,9 +2235,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Doctors retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الأطباء بنجاح" } }, "xml": { @@ -2264,9 +2348,13 @@ "photoUrl": {} } }, - "message": { + "messageEn": { "type": "string", "example": "Doctor retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الطبيب بنجاح" } }, "xml": { @@ -2333,9 +2421,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Doctor registered successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تسجيل الطبيب بنجاح" } }, "xml": { @@ -2430,9 +2522,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Doctor logged in successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تسجيل دخول الطبيب بنجاح" } }, "xml": { @@ -2482,9 +2578,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Password updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث كلمة المرور بنجاح" } }, "xml": { @@ -2567,9 +2667,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Clinic created successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إنشاء العيادة بنجاح" } }, "xml": { @@ -2651,9 +2755,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Doctor's clinics retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع عيادات الطبيب بنجاح" } }, "xml": { @@ -2737,9 +2845,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Clinic retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات العيادة بنجاح" } }, "xml": { @@ -2819,9 +2931,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Clinic updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث العيادة بنجاح" } }, "xml": { @@ -2858,9 +2974,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Clinic deleted successfully" + }, + "messageAr": { + "type": "string", + "example": "تم حذف العيادة بنجاح" } }, "xml": { @@ -2902,9 +3022,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Profile picture updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث صورة الملف الشخصي بنجاح" } }, "xml": { @@ -2943,9 +3067,13 @@ } } }, - "message": { + "messageEn": { "type": "string", "example": "Profile picture retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع صورة الملف الشخصي بنجاح" } }, "xml": { @@ -2975,9 +3103,13 @@ "schema": { "type": "object", "properties": { - "message": { + "messageEn": { "type": "string", "example": "Profile picture deleted successfully" + }, + "messageAr": { + "type": "string", + "example": "تم حذف صورة الملف الشخصي بنجاح" } }, "xml": { From 24a3c986033a5c8b778b8e1a6d7798f18ed3fbd8 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 28 Jan 2026 21:17:31 +0200 Subject: [PATCH 086/210] get patient selected appointment --- src/controllers/appointment.controller.ts | 15 +++ src/routes/appointment.route.ts | 54 ++++++++ src/services/appointment.service.ts | 44 +++++++ src/swagger-output.json | 99 +++++++++++++++ src/utils/responseMessages.ts | 144 ++++++++++++++++++++++ 5 files changed, 356 insertions(+) create mode 100644 src/utils/responseMessages.ts diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index f05a585..b7c3741 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -102,4 +102,19 @@ export class AppointmentController { data: appointments, }); }); + + public getPatientSelectedAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const { appointmentId } = req.params; + + if (!patientId) { + const error = createBilingualError(400, ErrorMessages.PATIENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appointment = await this.appointmentService.getPatientSelectedAppointment(appointmentId, patientId); + res.status(200).json({ + data: appointment, + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 654bc84..81fda55 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -346,6 +346,60 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.getPatientAppointments ); + + this.router.get( + `${this.path}/patient/:patientId/appointment/:appointmentId`, + /* + #swagger.path = '/appointments/patient/{patientId}/appointment/{appointmentId}' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Get details of a specific appointment for a patient' + #swagger.parameters['patientId'] = { + in: 'path', + description: 'Patient ID', + required: true, + type: 'string' + } + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Appointment details retrieved successfully', + schema: { + data: { + id: 'appointment-uuid', + status: 'CONFIRMED', + is_online: true, + slot_duration: 20, + doctor_name: 'House', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:20', + clinic_name: 'Medical Park Clinic', + clinic_address: '123 Main Street, New Cairo' + }, + message: 'Appointment details retrieved successfully' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, + this.appointmentController.getPatientSelectedAppointment + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 6a7e6fd..7050c53 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -246,6 +246,50 @@ export class AppointmentService { })); } + public async getPatientSelectedAppointment(appointmentId: string, patientId: string): Promise { + const appointment = await prisma.appointment.findFirst({ + where: { + id: appointmentId, + patient_id: patientId, + }, + select: { + id: true, + scheduled_time: true, + status: true, + is_online: true, + slot_duration: true, + end_time: true, + doctor: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + } + }); + if (!appointment) { + return null; + } + + return { + id: appointment.id, + status: appointment.status, + is_online: appointment.is_online, + slot_duration: appointment.slot_duration, + doctor_name: appointment.doctor.name, + appointment_date: this.formatDate(appointment.scheduled_time), + start_time: this.formatTime(appointment.scheduled_time), + end_time: this.formatTime(appointment.end_time), + clinic_name: appointment.clinic ? appointment.clinic.name : null, + clinic_address: appointment.clinic ? appointment.clinic.address : null, + }; + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/swagger-output.json b/src/swagger-output.json index b23a94c..58641cd 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3507,6 +3507,105 @@ } } } + }, + "/appointments/patient/{patientId}/appointment/{appointmentId}": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get details of a specific appointment for a patient", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "Patient ID" + }, + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Appointment details retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "is_online": { + "type": "boolean", + "example": true + }, + "slot_duration": { + "type": "number", + "example": 20 + }, + "doctor_name": { + "type": "string", + "example": "House" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-03" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "09:20" + }, + "clinic_name": { + "type": "string", + "example": "Medical Park Clinic" + }, + "clinic_address": { + "type": "string", + "example": "123 Main Street, New Cairo" + } + } + }, + "message": { + "type": "string", + "example": "Appointment details retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts new file mode 100644 index 0000000..32c6217 --- /dev/null +++ b/src/utils/responseMessages.ts @@ -0,0 +1,144 @@ +export const SuccessResponseMessages = { + // Success messages for Auth + SIGNED_UP_SUCCESSFULLY: { + message_en: "Signed up successfully.", + message_ar: "تم انشاء حساب جديد بنجاح.", + }, + LOGGED_IN_SUCCESSFULLY: { + message_en: "Logged in successfully.", + message_ar: "تم تسجيل الدخول بنجاح.", + }, + LOGGED_OUT_SUCCESSFULLY: { + message_en: "Logged out successfully.", + message_ar: "تم تسجيل الخروج بنجاح.", + }, + TOKEN_REFRESHED_SUCCESSFULLY: { + message_en: "Token refreshed successfully.", + message_ar: "تم تحديث رمز الدخول بنجاح.", + }, + PROFILE_COMPLETED_SUCCESSFULLY: { + message_en: "Profile completed successfully.", + message_ar: "تم إكمال الملف الشخصي بنجاح.", + }, + OTP_VERIFIED_SUCCESSFULLY: { + message_en: "OTP verified successfully.", + message_ar: "تم التحقق من رمز التحقق بنجاح.", + }, + PASSWORD_RESET_EMAIL_SENT_SUCCESSFULLY: { + message_en: "Password reset email sent successfully.", + message_ar: "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح.", + }, + PASSWORD_RESET_SUCCESSFULLY: { + message_en: "Password reset successfully.", + message_ar: "تم إعادة تعيين كلمة المرور بنجاح.", + }, + OTP_RESENT_SUCCESSFULLY: { + message_en: "OTP resent successfully.", + message_ar: "تم إعادة إرسال رمز التحقق بنجاح.", + }, + + // Success messages for Doctors by Admin + DOCTOR_CREATED: { + message_en: "Doctor created successfully.", + message_ar: "تم إنشاء حساب الطبيب بنجاح.", + }, + DOCTOR_RETRIEVED: { + message_en: "Doctor retrieved successfully.", + message_ar: "تم استرجاع بيانات الطبيب بنجاح.", + }, + DOCTORS_RETRIEVED: { + message_en: "Doctors retrieved successfully.", + message_ar: "تم استرجاع بيانات الأطباء بنجاح.", + }, + UNVERIFIED_DOCTORS_RETRIEVED: { + message_en: "Unverified doctors retrieved successfully.", + message_ar: "تم استرجاع بيانات الأطباء غير المعتمدين بنجاح.", + }, + DOCTOR_VERIFICATION_STATUS_UPDATED: { + message_en: "Doctor verification status updated successfully.", + message_ar: "تم تحديث حالة اعتماد الطبيب بنجاح.", + }, + + // Success messages for Clinics + CLINIC_CREATED_SUCCESSFULLY: { + message_en: "Clinic created successfully.", + message_ar: "تم إنشاء العيادة بنجاح.", + }, + CLINIC_RETRIEVED: { + message_en: "Clinic data retrieved successfully.", + message_ar: "تم استرجاع بيانات العيادة بنجاح.", + }, + CLINIC_UPDATED_SUCCESSFULLY: { + message_en: "Clinic data updated successfully.", + message_ar: "تم تحديث بيانات العيادة بنجاح.", + }, + CLINIC_DELETED_SUCCESSFULLY: { + message_en: "Clinic deleted successfully.", + message_ar: "تم حذف العيادة بنجاح.", + }, + CLINIC_DOCTORS_RETRIEVED: { + message_en: "Clinic's doctors retrieved successfully.", + message_ar: "تم استرجاع أطباء العيادة بنجاح.", + }, + + // Success messages for Doctors + DOCTOR_CREATED_WAITING_VERIFICATION: { + message_en: "Doctor account created successfully. Please wait for verification.", + message_ar: "تم إنشاء حساب الطبيب بنجاح. يرجى الانتظار للموافقة عليه.", + }, + PASSWORD_SET_SUCCESSFULLY_BY_DOCTOR: { + message_en: "Password set successfully.", + message_ar: "تم تعيين كلمة المرور بنجاح.", + }, + + // Success messages for Google Auth + PHONE_NUMBER_UPDATED_SUCCESSFULLY: { + message_en: "Phone number updated successfully.", + message_ar: "تم تحديث رقم الهاتف بنجاح.", + }, + GOOGLE_USER_DATA_RETRIEVED: { + message_en: "Google user data retrieved successfully.", + message_ar: "تم استرجاع بيانات مستخدم جوجل بنجاح.", + }, + + // Success messages for Super Admin + ADMIN_ADDED_SUCCESSFULLY: { + message_en: "Admin added successfully.", + message_ar: "تم إضافة المسؤول بنجاح.", + }, + ADMINS_RETRIEVED_SUCCESSFULLY: { + message_en: "Admins retrieved successfully.", + message_ar: "تم استرجاع بيانات المسؤولين بنجاح.", + }, + ADMIN_RETRIEVED_SUCCESSFULLY: { + message_en: "Admin retrieved successfully.", + message_ar: "تم استرجاع بيانات المسؤول بنجاح.", + }, + + // Success messages for User + PROFILE_PICTURE_UPDATED_SUCCESSFULLY: { + message_en: "Profile picture updated successfully.", + message_ar: "تم تحديث صورة الملف الشخصي بنجاح.", + }, + PROFILE_PICTURE_RETRIEVED_SUCCESSFULLY: { + message_en: "Profile picture retrieved successfully.", + message_ar: "تم استرجاع صورة الملف الشخصي بنجاح.", + }, + PROFILE_PICTURE_DELETED_SUCCESSFULLY: { + message_en: "Profile picture deleted successfully.", + message_ar: "تم حذف صورة الملف الشخصي بنجاح.", + }, + +} + +interface MultiLangMessageObj { + message_en: string; + message_ar: string; +} + +export const createMultiLangMessage = (multiLangMessageObj: MultiLangMessageObj) => { + return { + messageEn: multiLangMessageObj.message_en, + messageAr: multiLangMessageObj.message_ar, + }; +} \ No newline at end of file From a2654c4d4e1a959c36a087de72ba422f86fa8b76 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 28 Jan 2026 23:00:44 +0200 Subject: [PATCH 087/210] Reschedule an existing appointment --- src/controllers/appointment.controller.ts | 21 +++++ src/routes/appointment.route.ts | 73 +++++++++++++- src/services/appointment.service.ts | 26 +++++ src/swagger-output.json | 110 ++++++++++++++++++---- 4 files changed, 212 insertions(+), 18 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index b7c3741..af6674f 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -117,4 +117,25 @@ export class AppointmentController { data: appointment, }); }); + + public rescheduleAppointmentByPatient = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const { appointmentId } = req.params; + const { newScheduledTime } = req.body; + + if (!patientId) { + const error = createBilingualError(400, ErrorMessages.PATIENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!newScheduledTime) { + const error = createBilingualError(400, ErrorMessages.SCHEDULED_TIME_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); + res.status(200).json({ + message: 'Appointment rescheduled successfully', + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 81fda55..e129b52 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -309,7 +309,7 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Get all appointments for a specific patient' + #swagger.description = 'Get all appointments for a specific patient. Note: clinic_name and clinic_address will be null for online appointments' #swagger.parameters['patientId'] = { in: 'path', description: 'Patient ID', @@ -331,6 +331,18 @@ export class AppointmentRoute implements Routes { end_time: '09:20', clinic_name: 'Medical Park Clinic', clinic_address: '123 Main Street, New Cairo' + }, + { + id: 'appointment-uuid-2', + status: 'CONFIRMED', + is_online: true, + slot_duration: 30, + doctor_name: 'Wilson', + appointment_date: '2026-02-05', + start_time: '14:00', + end_time: '14:30', + clinic_name: null, + clinic_address: null } ], message: 'Patient appointments retrieved successfully' @@ -359,7 +371,7 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Get details of a specific appointment for a patient' + #swagger.description = 'Get details of a specific appointment for a patient. Note: clinic_name and clinic_address will be null for online appointments' #swagger.parameters['patientId'] = { in: 'path', description: 'Patient ID', @@ -400,6 +412,63 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.getPatientSelectedAppointment ); + + this.router.patch( + `${this.path}/patient/:patientId/appointment/:appointmentId/reschedule`, + /* + #swagger.path = '/appointments/patient/{patientId}/appointment/{appointmentId}/reschedule' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Reschedule an existing appointment to a new time slot' + #swagger.parameters['patientId'] = { + in: 'path', + description: 'Patient ID', + required: true, + type: 'string' + } + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID to reschedule', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'New scheduled time for the appointment', + required: true, + schema: { + newScheduledTime: '2026-02-05T11:00:00.000Z' + } + } + #swagger.responses[200] = { + description: 'Appointment rescheduled successfully', + schema: { + message: 'Appointment rescheduled successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid time or slot not available', + schema: { + message: 'Error message describing the issue' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + + AuthMiddleware, + this.appointmentController.rescheduleAppointmentByPatient + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 7050c53..e0a9f82 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -290,6 +290,32 @@ export class AppointmentService { }; } + public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { + const slotDuration = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + patient_id: patientId, + }, + select: { + slot_duration: true, + }, + }); + const newEndTime = new Date(newScheduledTime.getTime() + slotDuration.slot_duration * 60000); + + await prisma.appointment.update({ + where: { + id: appointmentId, + patient_id: patientId, + }, + data: { + scheduled_time: newScheduledTime, + end_time: newEndTime, + } + }); + + // penalty to be added later + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/swagger-output.json b/src/swagger-output.json index 58641cd..538c7dc 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3418,7 +3418,7 @@ "tags": [ "Appointments" ], - "description": "Get all appointments for a specific patient", + "description": "Get all appointments for a specific patient. Note: clinic_name and clinic_address will be null for online appointments", "parameters": [ { "name": "patientId", @@ -3448,7 +3448,7 @@ "properties": { "id": { "type": "string", - "example": "appointment-uuid" + "example": "appointment-uuid-2" }, "status": { "type": "string", @@ -3460,32 +3460,26 @@ }, "slot_duration": { "type": "number", - "example": 20 + "example": 30 }, "doctor_name": { "type": "string", - "example": "House" + "example": "Wilson" }, "appointment_date": { "type": "string", - "example": "2026-02-03" + "example": "2026-02-05" }, "start_time": { "type": "string", - "example": "09:00" + "example": "14:00" }, "end_time": { "type": "string", - "example": "09:20" - }, - "clinic_name": { - "type": "string", - "example": "Medical Park Clinic" + "example": "14:30" }, - "clinic_address": { - "type": "string", - "example": "123 Main Street, New Cairo" - } + "clinic_name": {}, + "clinic_address": {} } } }, @@ -3513,7 +3507,7 @@ "tags": [ "Appointments" ], - "description": "Get details of a specific appointment for a patient", + "description": "Get details of a specific appointment for a patient. Note: clinic_name and clinic_address will be null for online appointments", "parameters": [ { "name": "patientId", @@ -3606,6 +3600,90 @@ } } } + }, + "/appointments/patient/{patientId}/appointment/{appointmentId}/reschedule": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Reschedule an existing appointment to a new time slot", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "Patient ID" + }, + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID to reschedule" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "New scheduled time for the appointment", + "required": true, + "schema": { + "type": "object", + "properties": { + "newScheduledTime": { + "type": "string", + "example": "2026-02-05T11:00:00.000Z" + } + } + } + } + ], + "responses": { + "200": { + "description": "Appointment rescheduled successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Appointment rescheduled successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid time or slot not available", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file From b3fa780dd875a3ad677a4d4726aa8558e6a35596 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 29 Jan 2026 14:46:01 +0200 Subject: [PATCH 088/210] updated multer config for pdf file upload --- src/middlewares/multer.middleware.ts | 26 +++++++++++++++++++++----- src/routes/user.route.ts | 4 ++-- src/utils/errorMessages.ts | 6 +++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/middlewares/multer.middleware.ts b/src/middlewares/multer.middleware.ts index de39cb1..e9a0c8b 100644 --- a/src/middlewares/multer.middleware.ts +++ b/src/middlewares/multer.middleware.ts @@ -17,23 +17,39 @@ const storage = multer.diskStorage({ }); // 2. Filter to accept ONLY images -const fileFilter = (req: Request, file: Express.Multer.File, cb: (error: Error | null, acceptFile: boolean) => void) => { +const imageFilter = (req: Request, file: Express.Multer.File, cb: (error: Error | null, acceptFile: boolean) => void) => { if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { cb(null, true); // Accept file } else { - const bilingualError = createBilingualError(400, ErrorMessages.UNSUPPORTED_FILE_FORMAT); + const bilingualError = createBilingualError(400, ErrorMessages.UNSUPPORTED_IMAGE_FILE_FORMAT); + const error = new HttpException(bilingualError.status, bilingualError.message, bilingualError.messageAr); + cb(error, false); // Reject file + } +}; + +const pdfFilter = (req: Request, file: Express.Multer.File, cb: (error: Error | null, acceptFile: boolean) => void) => { + if (file.mimetype === 'application/pdf') { + cb(null, true); // Accept file + } else { + const bilingualError = createBilingualError(400, ErrorMessages.UNSUPPORTED_FILE_FORMAT_PDF); const error = new HttpException(bilingualError.status, bilingualError.message, bilingualError.messageAr); cb(error, false); // Reject file } }; // 3. Initialize Multer with limits -const upload = multer({ +export const uploadImage = multer({ storage: storage, - fileFilter: fileFilter, + fileFilter: imageFilter, limits: { fileSize: 1024 * 1024 * 3 // Limit file size to 3MB } }); -export default upload; \ No newline at end of file +export const uploadPdf = multer({ + storage: storage, + fileFilter: pdfFilter, + limits: { + fileSize: 1024 * 1024 * 10 // Limit file size to 10MB + } +}); \ No newline at end of file diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 83fa798..3fc66f5 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -1,7 +1,7 @@ import { UsersController } from "@/controllers/user.controller"; import { Routes } from "@/interfaces"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import upload from "@/middlewares/multer.middleware"; +import {uploadImage} from "@/middlewares/multer.middleware"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { errorWrapper } from "@/utils/errorWrapper"; import { Router } from "express"; @@ -43,7 +43,7 @@ export class UsersRoute implements Routes { } */ AuthMiddleware, - upload.single('profilePicture'), + uploadImage.single('profilePicture'), ValidationMiddleware(null, false, false, false, true), errorWrapper(this.usersController.updateProfilePicture) ); diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 5c8cecb..155be25 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -108,10 +108,14 @@ export const ErrorMessages = { en: 'No file uploaded', ar: 'لم يتم تحميل أي ملف', }, - UNSUPPORTED_FILE_FORMAT: { + UNSUPPORTED_IMAGE_FILE_FORMAT: { en: 'Unsupported file format. Only JPEG and PNG allowed.', ar: 'تنسيق ملف غير مدعوم. يُسمح فقط بملفات JPEG و PNG.', }, + UNSUPPORTED_FILE_FORMAT_PDF: { + en: 'Unsupported file format. Only PDF allowed.', + ar: 'تنسيق ملف غير مدعوم. يُسمح فقط بملفات PDF.', + }, NO_PROFILE_PICTURE: { en: 'No profile picture found', ar: 'لم يتم العثور على صورة الملف الشخصي', From 505f840de8f4ecfc701cd56970e9df9660223dd9 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 29 Jan 2026 14:48:09 +0200 Subject: [PATCH 089/210] cancel appointment by doctor or patient --- src/controllers/appointment.controller.ts | 10 +++ src/routes/appointment.route.ts | 50 ++++++++++++++ src/services/appointment.service.ts | 43 ++++++++++++ src/swagger-output.json | 82 +++++++++++++++++++++++ src/utils/errorMessages.ts | 13 ++++ 5 files changed, 198 insertions(+) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index af6674f..232772b 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -138,4 +138,14 @@ export class AppointmentController { message: 'Appointment rescheduled successfully', }); }); + + public cancelAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const userId = req.user.id; + const { appointmentId } = req.params; + + await this.appointmentService.cancelAppointment(userId, appointmentId); + res.status(200).json({ + message: 'Appointment cancelled successfully', + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e129b52..9556e88 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -469,6 +469,56 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.rescheduleAppointmentByPatient ); + + this.router.delete( + `${this.path}/:appointmentId/cancel`, + /* + #swagger.path = '/appointments/{appointmentId}/cancel' + #swagger.method = 'delete' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Cancel an appointment (soft delete). Can be cancelled by either patient or doctor.' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID to cancel', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Appointment cancelled successfully', + schema: { + success: true, + message: 'Appointment cancelled successfully', + data: { + appointmentId: 'appointment-uuid', + cancelledAt: '2026-01-29T12:00:00.000Z' + } + } + } + #swagger.responses[400] = { + description: 'Bad request - appointment already cancelled, completed, or too late to cancel', + schema: { + message: 'Error message describing the issue' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - user is not the patient or doctor of this appointment' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, + this.appointmentController.cancelAppointment + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index e0a9f82..b3132c1 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -316,6 +316,49 @@ export class AppointmentService { // penalty to be added later } + public async cancelAppointment(userId: string, appointmentId: string): Promise { + // see whether the user is patient or doctor + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + }, + select: { + id: true, + patient_id: true, + doctor_id: true, + deleted_at: true, + } + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.deleted_at) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.patient_id !== userId && appointment.doctor_id !== userId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_APPOINTMENT_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.appointment.update({ + where: { + id: appointmentId, + }, + data: { + cancelled_by: appointment.patient_id === userId ? 'PATIENT' : 'DOCTOR', + deleted_at: new Date(), + modified_at: new Date(), + status: 'CANCELLED', + } + }); + // penalty to be added later + }; + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/swagger-output.json b/src/swagger-output.json index 538c7dc..83a4b1d 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3684,6 +3684,88 @@ } } } + }, + "/appointments/{appointmentId}/cancel": { + "delete": { + "tags": [ + "Appointments" + ], + "description": "Cancel an appointment (soft delete). Can be cancelled by either patient or doctor.", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID to cancel" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Appointment cancelled successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Appointment cancelled successfully" + }, + "data": { + "type": "object", + "properties": { + "appointmentId": { + "type": "string", + "example": "appointment-uuid" + }, + "cancelledAt": { + "type": "string", + "example": "2026-01-29T12:00:00.000Z" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - appointment already cancelled, completed, or too late to cancel", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + }, + "403": { + "description": "Forbidden - user is not the patient or doctor of this appointment" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index c387b84..740cc28 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -172,6 +172,19 @@ export const ErrorMessages = { en: 'Something went wrong', ar: 'حدث خطأ ما', }, + + APPOINTMENT_NOT_FOUND: { + en: "Appointment not found", + ar: "الموعد غير موجود" + }, + UNAUTHORIZED_APPOINTMENT_ACCESS: { + en: "You are not authorized to access this appointment", + ar: "غير مصرح لك بالوصول إلى هذا الموعد" + }, + APPOINTMENT_ALREADY_DELETED: { + en: "Appointment has already been deleted", + ar: "تم حذف الموعد بالفعل" + }, }; // Helper function to create bilingual error From 88a3b49313ba406687500d83700ce0383c85d437 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 29 Jan 2026 16:23:26 +0200 Subject: [PATCH 090/210] reschedule an appointment by doctor doctor can shift an appointment by a number of minutes or set a new scheduled time --- src/controllers/appointment.controller.ts | 26 ++++ src/routes/appointment.route.ts | 56 +++++++++ src/services/appointment.service.ts | 138 ++++++++++++++++++++++ src/swagger-output.json | 84 +++++++++++++ src/utils/errorMessages.ts | 20 ++++ 5 files changed, 324 insertions(+) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 232772b..a0fb843 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -148,4 +148,30 @@ export class AppointmentController { message: 'Appointment cancelled successfully', }); }); + + public rescheduleAppointmentByDoctor = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { appointmentId } = req.params; + const { minutes, newScheduledTime } = req.body; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!minutes && !newScheduledTime) { + const error = createBilingualError(400, ErrorMessages.INVALID_RESCHEDULE_PARAMETERS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (minutes && newScheduledTime) { + const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined); + res.status(200).json({ + message: 'Appointment rescheduled successfully', + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 9556e88..816e0cf 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -519,6 +519,62 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.cancelAppointment ); + + this.router.patch( + `${this.path}/doctor/:appointmentId/reschedule`, + /* + #swagger.path = '/appointments/doctor/{appointmentId}/reschedule' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Reschedule an appointment by the doctor. Doctor can either shift the appointment by a number of minutes or set a new scheduled time (but not both)' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID to reschedule', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Reschedule parameters (provide either minutes OR newScheduledTime)', + required: true, + schema: { + minutes: 15, + newScheduledTime: '2026-02-05T11:30:00.000Z' + } + } + #swagger.responses[200] = { + description: 'Appointment rescheduled successfully', + schema: { + message: 'Appointment rescheduled successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid reschedule parameters', + schema: { + message: 'Error message describing the issue' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - appointment does not belong to the authenticated doctor' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, + this.appointmentController.rescheduleAppointmentByDoctor + ); + + } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index b3132c1..78c78f0 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -316,6 +316,34 @@ export class AppointmentService { // penalty to be added later } + public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes?: number, newScheduledTime?: Date) : Promise { + const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); + + let updatedScheduledTime: Date; + let updatedEndTime: Date; + + if (minutes){ + updatedScheduledTime = new Date(appointment.scheduled_time.getTime() + minutes * 60000); + updatedEndTime = new Date(appointment.end_time.getTime() + minutes * 60000); + } else { + updatedScheduledTime = newScheduledTime; + updatedEndTime = new Date(newScheduledTime.getTime() + appointment.slot_duration * 60000); + } + + await this.validateDoctorAvailability(doctorId, appointment.clinic_id, updatedScheduledTime, updatedEndTime, appointmentId); + + await prisma.appointment.update({ + where: { + id: appointmentId, + }, + data: { + scheduled_time: updatedScheduledTime, + end_time: updatedEndTime, + modified_at: new Date(), + } + }); + } + public async cancelAppointment(userId: string, appointmentId: string): Promise { // see whether the user is patient or doctor const appointment = await prisma.appointment.findUnique({ @@ -445,4 +473,114 @@ export class AppointmentService { }); return availability_type === 'ONLINE' || availability_type === 'BOTH'; } + + private async getAndValidateAppointment(appointmentId: string, doctorId: string) { + const appointment = await prisma.appointment.findUnique({ + where: { id: appointmentId }, + select: { + id: true, + patient_id: true, + doctor_id: true, + clinic_id: true, + scheduled_time: true, + end_time: true, + slot_duration: true, + status: true, + deleted_at: true, + } + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_APPOINTMENT_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.deleted_at) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return appointment; + } + + private async validateDoctorAvailability(doctorId: string, clinicId: string | null, newScheduledTime: Date, newEndTime: Date, excludeAppointmentId?: string): Promise { + + // check if doctor works on this day + const dayOfWeek = this.getDayOfWeek(newScheduledTime.getDay()); + const isOnline = await this.doctorIsOnline(doctorId); + + const schedule = await prisma.doctorSchedule.findFirst({ + where: { + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + day_of_week: dayOfWeek, + is_active: true, + deleted_at: null, + }, + select: { + start_time: true, + end_time: true, + } + }); + + if (!schedule) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_NOT_WORKING_ON_DAY); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // check if the new time within schedule or not + const scheduleStart = this.parseTimeToDate(newScheduledTime, this.formatTime(schedule.start_time)); + const scheduleEnd = this.parseTimeToDate(newScheduledTime, this.formatTime(schedule.end_time)); + + if (newScheduledTime < scheduleStart || newEndTime > scheduleEnd) { + const error = createBilingualError(400, ErrorMessages.TIME_OUTSIDE_SCHEDULE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + // check for any conflicts with existing appointments (appointments on the same calendar day) + const startOfDay = new Date(newScheduledTime); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(newScheduledTime); + endOfDay.setHours(23, 59, 59, 999); + + const whereClause: any = { + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay, + }, + status: { in: ['CONFIRMED', 'COMPLETED'] }, + deleted_at: null, + }; + + // exclude the appointment being rescheduled + if (excludeAppointmentId) { + whereClause.id = { not: excludeAppointmentId }; + } + + const conflictingAppointments = await prisma.appointment.findMany({ + where: whereClause, + select: { + scheduled_time: true, + end_time: true, + } + }); + + // check for overlap + const hasConflict = conflictingAppointments.some(existing => { + return this.doesSlotOverlap(newScheduledTime,newEndTime, new Date(existing.scheduled_time), new Date(existing.end_time)); + }); + + if (hasConflict) { + const error = createBilingualError(400, ErrorMessages.TIME_SLOT_NOT_AVAILABLE); + throw new HttpException(error.status, error.message, error.messageAr); + } + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 83a4b1d..1d64000 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3766,6 +3766,90 @@ } } } + }, + "/appointments/doctor/{appointmentId}/reschedule": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Reschedule an appointment by the doctor. Doctor can either shift the appointment by a number of minutes or set a new scheduled time (but not both)", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID to reschedule" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Reschedule parameters (provide either minutes OR newScheduledTime)", + "required": true, + "schema": { + "type": "object", + "properties": { + "minutes": { + "type": "number", + "example": 15 + }, + "newScheduledTime": { + "type": "string", + "example": "2026-02-05T11:30:00.000Z" + } + } + } + } + ], + "responses": { + "200": { + "description": "Appointment rescheduled successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Appointment rescheduled successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid reschedule parameters", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - appointment does not belong to the authenticated doctor" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 740cc28..be99ee2 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -116,6 +116,14 @@ export const ErrorMessages = { en: 'No profile picture found', ar: 'لم يتم العثور على صورة الملف الشخصي', }, + DOCTOR_NOT_WORKING_ON_DAY: { + en: "Doctor is not available on this day", + ar: "الطبيب غير متاح في هذا اليوم" + }, + TIME_OUTSIDE_SCHEDULE: { + en: "Requested time is outside doctor's working hours", + ar: "الوقت المطلوب خارج ساعات عمل الطبيب" + }, // Clinic errors CLINIC_NOT_FOUND: { @@ -167,6 +175,14 @@ export const ErrorMessages = { en: 'Cannot book appointment in the past', ar: 'لا يمكن حجز موعد في الماضي', }, + TIME_SLOT_NOT_AVAILABLE: { + en: "This time slot is not available", + ar: "هذا الوقت غير متاح" + }, + EITHER_MINUTES_OR_NEW_TIME: { + en: "Provide either shift minutes or new scheduled time, not both", + ar: "يرجى تقديم إما دقائق التغيير أو وقت موعد جديد، وليس كلاهما" + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', @@ -185,6 +201,10 @@ export const ErrorMessages = { en: "Appointment has already been deleted", ar: "تم حذف الموعد بالفعل" }, + INVALID_RESCHEDULE_PARAMETERS: { + en: "Provide either new scheduled time or shift minutes", + ar: "يرجى تقديم وقت موعد جديد أو عدد دقائق التغيير" + } }; // Helper function to create bilingual error From 5faecff51f78d5cbcb0fa1d550a5932a2c6642ed Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 29 Jan 2026 19:07:34 +0200 Subject: [PATCH 091/210] reschedule multiple appointments by doctor doctor can bulk reschedule multiple appointments 1) When using minutes, all appointments are shifted by the same number of minutes. 3) When using : - If is true, appointments keep their original slots but move to the new date - If is false, appointments are reallocated sequentially based on the doctor schedule --- src/controllers/appointment.controller.ts | 25 ++++++ src/routes/appointment.route.ts | 60 +++++++++++++++ src/services/appointment.service.ts | 79 +++++++++++++++++++ src/swagger-output.json | 92 +++++++++++++++++++++++ 4 files changed, 256 insertions(+) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index a0fb843..f2ce7c6 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -174,4 +174,29 @@ export class AppointmentController { message: 'Appointment rescheduled successfully', }); }); + + public bulkRescheduleByDoctor = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { appointmentIds, minutes, newScheduledTime, keepOriginalSlots } = req.body; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!minutes && !newScheduledTime) { + const error = createBilingualError(400, ErrorMessages.INVALID_RESCHEDULE_PARAMETERS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (minutes && newScheduledTime) { + const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.bulkRescheduleByDoctor(doctorId, appointmentIds, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined, keepOriginalSlots); + res.status(200).json({ + message: 'Appointments rescheduled successfully', + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 816e0cf..de6448b 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -574,6 +574,66 @@ export class AppointmentRoute implements Routes { this.appointmentController.rescheduleAppointmentByDoctor ); + this.router.patch( + `${this.path}/doctor/bulk-reschedule`, + /* + #swagger.path = '/appointments/doctor/bulk-reschedule' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Bulk reschedule multiple appointments by the authenticated doctor. \ + Rules: \ + (1) You must provide EITHER "minutes" OR "newScheduledTime" (not both). \ + (2) When using "minutes", all appointments are shifted by the same number of minutes. \ + (3) When using "newScheduledTime": \ + - If "keepOriginalSlots" is true, appointments keep their original time-of-day but move to the new date. \ + - If "keepOriginalSlots" is false, appointments are reallocated sequentially based on the doctor schedule.' + + #swagger.parameters['body'] = { + in: 'body', + description: 'Bulk reschedule parameters', + required: true, + schema: { + appointmentIds: [ + 'appointment-uuid-1', + 'appointment-uuid-2', + 'appointment-uuid-3' + ], + minutes: 15, + newScheduledTime: '2026-02-10T09:00:00.000Z', + keepOriginalSlots: true + } + } + #swagger.responses[200] = { + description: 'Appointments rescheduled successfully', + schema: { + message: 'Appointments rescheduled successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid or conflicting reschedule parameters', + schema: { + message: 'Error message describing the issue' + } + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - one or more appointments do not belong to the authenticated doctor' + } + #swagger.responses[404] = { + description: 'One or more appointments not found' + } + */ + AuthMiddleware, + this.appointmentController.bulkRescheduleByDoctor + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 78c78f0..a007b20 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -6,6 +6,7 @@ import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { PatientAppointment } from '@/interfaces/appointments.interface'; +import { min } from 'class-validator'; @Service() export class AppointmentService { @@ -344,6 +345,84 @@ export class AppointmentService { }); } + public async bulkRescheduleByDoctor(doctorId: string, appointmentIds: string[], minutes?: number, newBaseDate?: Date, keepOriginalSlots?: boolean): Promise { + if (minutes) { + for (const appointmentId of appointmentIds) { + await this.getAndValidateAppointment(appointmentId, doctorId); + await this.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes, undefined); + } + } + + if (newBaseDate) { + if (keepOriginalSlots) { + for (const appointmentId of appointmentIds) { + await this.getAndValidateAppointment(appointmentId, doctorId); + + const appointment = await prisma.appointment.findUnique({ + where: { id: appointmentId }, + select: { scheduled_time: true }, + }); + const originalTime = new Date(appointment.scheduled_time); + + const newScheduledTime = new Date(newBaseDate); + newScheduledTime.setHours(originalTime.getHours(), originalTime.getMinutes(), 0, 0); + + await this.rescheduleAppointmentByDoctor(doctorId, appointmentId, null, newScheduledTime); + } + } + else { + const appointments = await prisma.appointment.findMany({ + where: { + id: { in: appointmentIds }, + doctor_id: doctorId, + status: { in: ['CONFIRMED'] }, + deleted_at: null, + }, + select: { + id: true, + scheduled_time: true, + slot_duration: true, + clinic_id: true, + }, + orderBy: { + scheduled_time: 'asc', + } + + }); + const dayOfWeek = this.getDayOfWeek(newBaseDate.getDay()); + const isOnline = await this.doctorIsOnline(doctorId); + const clinicId = appointments[0]?.clinic_id || null; + + const schedule = await prisma.doctorSchedule.findFirst({ + where:{ + day_of_week: dayOfWeek, + doctor_id: doctorId, + clinic_id: isOnline ? null : clinicId, + is_active: true, + deleted_at: null, + }, + select:{ + slot_duration: true, + buffer_time: true, + } + }); + + let currentSlotStart = new Date(newBaseDate); + + for (let i = 0; i < appointments.length; i++) { + const appointment = appointments[i]; + await this.getAndValidateAppointment(appointment.id, doctorId); + const newScheduledTime = new Date(currentSlotStart); + + await this.rescheduleAppointmentByDoctor(doctorId, appointment.id, null, newScheduledTime); + + // move to next slot + currentSlotStart = new Date(currentSlotStart.getTime() + (schedule.slot_duration + schedule.buffer_time) * 60000); + } + } + } + } + public async cancelAppointment(userId: string, appointmentId: string): Promise { // see whether the user is patient or doctor const appointment = await prisma.appointment.findUnique({ diff --git a/src/swagger-output.json b/src/swagger-output.json index 1d64000..a74a2fa 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3850,6 +3850,98 @@ } } } + }, + "/appointments/doctor/bulk-reschedule": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Bulk reschedule multiple appointments by the authenticated doctor. \\ Rules: \\ (1) You must provide EITHER \"minutes\" OR \"newScheduledTime\" (not both). \\ (2) When using \"minutes\", all appointments are shifted by the same number of minutes. \\ (3) When using \"newScheduledTime\": \\ - If \"keepOriginalSlots\" is true, appointments keep their original time-of-day but move to the new date. \\ - If \"keepOriginalSlots\" is false, appointments are reallocated sequentially based on the doctor schedule.", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Bulk reschedule parameters", + "required": true, + "schema": { + "type": "object", + "properties": { + "appointmentIds": { + "type": "array", + "example": [ + "appointment-uuid-1", + "appointment-uuid-2", + "appointment-uuid-3" + ], + "items": { + "type": "string" + } + }, + "minutes": { + "type": "number", + "example": 15 + }, + "newScheduledTime": { + "type": "string", + "example": "2026-02-10T09:00:00.000Z" + }, + "keepOriginalSlots": { + "type": "boolean", + "example": true + } + } + } + } + ], + "responses": { + "200": { + "description": "Appointments rescheduled successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Appointments rescheduled successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid or conflicting reschedule parameters", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - one or more appointments do not belong to the authenticated doctor" + }, + "404": { + "description": "One or more appointments not found" + } + } + } } } } \ No newline at end of file From 932a6a893cf212f48bcee42bb7955f83314e6977 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 29 Jan 2026 19:14:53 +0200 Subject: [PATCH 092/210] Made uploading doctor verify file endpoint --- src/controllers/clinic.controller.ts | 21 +-- src/controllers/doctor.controller.ts | 3 +- src/dtos/doctors.dto.ts | 9 ++ src/interfaces/clinics.interface.ts | 4 +- src/interfaces/enums.interface.ts | 9 ++ .../migration.sql | 13 ++ src/prisma/schema.prisma | 26 +++- src/routes/doctors.route.ts | 11 +- src/services/doctor.service.ts | 145 +++++++++++++++--- src/utils/errorMessages.ts | 4 + 10 files changed, 199 insertions(+), 46 deletions(-) create mode 100644 src/prisma/migrations/20260129164535_doctor_verification_files_urls/migration.sql diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index c42b434..1e69581 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -2,6 +2,7 @@ import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser } from "@/interfaces"; import { ClinicService } from "@/services/clinic.service"; +import { catchAsync } from "@/utils/catchAsync"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; @@ -10,7 +11,7 @@ import Container from "typedi"; export class ClinicController { public clinicService = Container.get(ClinicService); - public createClinic = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + public createClinic = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const clinicData: CreateUpdateClinicRequestDto = req.body; const isAllowedToCreateClinic = await this.clinicService.isDoctorAllowedToCreateClinic(req.user.id); @@ -26,9 +27,9 @@ export class ClinicController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_CREATED_SUCCESSFULLY); res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); - } + }); - public getClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { + public getClinicById = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { const clinicId = req.params.id; const clinic = await this.clinicService.getClinicById(clinicId); @@ -38,9 +39,9 @@ export class ClinicController { } const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_RETRIEVED); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr, data: clinic }); - } + }); - public updateClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { + public updateClinicById = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { const clinicId = req.params.id; const clinicUpdateData: CreateUpdateClinicRequestDto = req.body; @@ -52,9 +53,9 @@ export class ClinicController { } const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_UPDATED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); - } + }); - public deleteClinicById = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + public deleteClinicById = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const clinicId = req.params.id; const isCreatingDoctor = await this.clinicService.isCreatingDoctorOfClinic(req.user.id, clinicId); @@ -66,9 +67,9 @@ export class ClinicController { await this.clinicService.deleteClinic(clinicId); const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_DELETED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); - } + }); - public getDoctorClinics = async (req: RequestWithUser, res: Response, next: NextFunction) => { + public getDoctorClinics = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { const doctorId = req.user?.id; const clinics = await this.clinicService.getDoctorClinics(doctorId); const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_DOCTORS_RETRIEVED); @@ -77,5 +78,5 @@ export class ClinicController { messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); - } + }); } diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 0d69c4c..5a7f1d4 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -14,7 +14,8 @@ export class DoctorController { public doctorSignup = async (req: Request, res: Response, next: NextFunction) => { const doctorData: DoctorSignupRequestDto = req.body; - await this.doctorService.signup(doctorData); + const doctorFiles = req.files as Express.Multer.File[]; + await this.doctorService.signup(doctorData , doctorFiles); const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_CREATED_WAITING_VERIFICATION); res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }; diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index 4442413..50b2300 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -27,6 +27,15 @@ export class DoctorSignupRequestDto { @IsString() public date_of_birth?: Date; + graduationCertificate: Express.Multer.File; + membershipCard: Express.Multer.File; + professionalPracticeCard: Express.Multer.File; + + mastersCertificate: Express.Multer.File; + fellowshipCertificate: Express.Multer.File; + unionSpecializationCertificate: Express.Multer.File; + + } export class DoctorLoginRequestDto { diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index 14e3875..9e1000f 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -3,8 +3,8 @@ import { User, Doctor } from './users.interface'; export interface Clinic { id: string; is_active: boolean; - opening_at: Date; - closing_at: Date; + opening_at: string; + closing_at: string; address: string; created_at: Date; modified_at: Date; diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index 68994e2..c8815e5 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -24,4 +24,13 @@ export enum RecordType { SCAN = 'SCAN', DIAGNOSIS = 'DIAGNOSIS', VISIT_SUMMARY = 'VISIT_SUMMARY' +} + +export enum DOCTOR_FILES { + GRADUATION_CERTIFICATE = 'graduationCertificate', + MEMBERSHIP_CARD = 'membershipCard', + PROFESSIONAL_PRACTICE_CARD = 'professionalPracticeCard', + MASTERS_CERTIFICATE = 'mastersCertificate', + FELLOWSHIP_CERTIFICATE = 'fellowshipCertificate', + UNION_SPECIALIZATION_CERTIFICATE = 'unionSpecializationCertificate', } \ No newline at end of file diff --git a/src/prisma/migrations/20260129164535_doctor_verification_files_urls/migration.sql b/src/prisma/migrations/20260129164535_doctor_verification_files_urls/migration.sql new file mode 100644 index 0000000..ba68ecd --- /dev/null +++ b/src/prisma/migrations/20260129164535_doctor_verification_files_urls/migration.sql @@ -0,0 +1,13 @@ +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "fellowshipCertificatePublicId" VARCHAR(500), +ADD COLUMN "fellowshipCertificateUrl" VARCHAR(500), +ADD COLUMN "graduationCertificatePublicId" VARCHAR(500), +ADD COLUMN "graduationCertificateUrl" VARCHAR(500), +ADD COLUMN "mastersCertificatePublicId" VARCHAR(500), +ADD COLUMN "mastersCertificateUrl" VARCHAR(500), +ADD COLUMN "membershipCardPublicId" VARCHAR(500), +ADD COLUMN "membershipCardUrl" VARCHAR(500), +ADD COLUMN "professionalPracticeCardPublicId" VARCHAR(500), +ADD COLUMN "professionalPracticeCardUrl" VARCHAR(500), +ADD COLUMN "unionSpecializationCertificatePublicId" VARCHAR(500), +ADD COLUMN "unionSpecializationCertificateUrl" VARCHAR(500); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 0c52fe2..9c23aec 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -51,14 +51,26 @@ model User { } model Doctor { - id String @id @default(uuid()) - specialization String @db.VarChar(255) - avg_time DateTime? @db.Time(0) - account_status DoctorAccountStatus @default(PENDING) - num_of_created_clinics Int @default(0) + id String @id @default(uuid()) + specialization String @db.VarChar(255) + avg_time DateTime? @db.Time(0) + account_status DoctorAccountStatus @default(PENDING) + num_of_created_clinics Int @default(0) + graduationCertificateUrl String? @db.VarChar(500) + graduationCertificatePublicId String? @db.VarChar(500) + membershipCardUrl String? @db.VarChar(500) + membershipCardPublicId String? @db.VarChar(500) + professionalPracticeCardUrl String? @db.VarChar(500) + professionalPracticeCardPublicId String? @db.VarChar(500) + mastersCertificateUrl String? @db.VarChar(500) + mastersCertificatePublicId String? @db.VarChar(500) + fellowshipCertificateUrl String? @db.VarChar(500) + fellowshipCertificatePublicId String? @db.VarChar(500) + unionSpecializationCertificateUrl String? @db.VarChar(500) + unionSpecializationCertificatePublicId String? @db.VarChar(500) // Relations - user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) - clinic_doctors ClinicDoctor[] + user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) + clinic_doctors ClinicDoctor[] @@map("Doctor") } diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 51cd377..f29ff52 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -6,6 +6,7 @@ import { Router } from "express"; import { errorWrapper } from "@/utils/errorWrapper"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { Role } from "@prisma/client"; +import { uploadPdf } from "@/middlewares/multer.middleware"; export class DoctorsRoute implements Routes { @@ -44,7 +45,15 @@ export class DoctorsRoute implements Routes { } } */ - ValidationMiddleware(DoctorSignupRequestDto), + uploadPdf.fields([ + { name: 'graduationCertificate', maxCount: 1 }, + { name: 'membershipCard', maxCount: 1 }, + { name: 'professionalPracticeCard', maxCount: 1 }, + { name: 'mastersCertificate', maxCount: 1 }, + { name: 'fellowshipCertificate', maxCount: 1 }, + { name: 'unionSpecializationCertificate', maxCount: 1 }, + ]), + ValidationMiddleware(DoctorSignupRequestDto, false, false, false, true), errorWrapper(this.doctorsController.doctorSignup) ); diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index d7ec89c..c4c21a5 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -6,16 +6,19 @@ import { DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; import { DoctorLoginData } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; -import { Clinic } from "@/interfaces"; +import prisma from "@/config/prisma"; +import cloudinary from "@/utils/cloudinary"; +import { UploadApiResponse } from "cloudinary"; +import { DOCTOR_FILES } from "@/interfaces"; +import fs from "fs"; + -// TO BE CHANGED -const prisma = new PrismaClient(); const authService = new AuthService(); @Service() export class DoctorService { - public async signup(doctorData: DoctorSignupRequestDto): Promise { + public async signup(doctorData: DoctorSignupRequestDto, doctorFiles: {}): Promise { // Check if email already exists const existingUser = await prisma.user.findUnique({ where: { email: doctorData.email } @@ -41,29 +44,42 @@ export class DoctorService { const hashedPassword = await hash(doctorData.password, 10); - // Create user with doctor role - const createdUser = await prisma.user.create({ - data: { - email: doctorData.email, - name: doctorData.name, - username, - phone: doctorData.phone, - gender: doctorData.gender, - date_of_birth: new Date(doctorData.date_of_birth), - password_hash: hashedPassword, - role: Role.DOCTOR, - isVerified: false, - hasCompletedProfile: true, - }, - }); + // Create user and doctor in a transaction + const createdUserId = await prisma.$transaction(async (tx) => { + const createdUser = await tx.user.create({ + data: { + email: doctorData.email, + name: doctorData.name, + username, + phone: doctorData.phone, + gender: doctorData.gender, + date_of_birth: new Date(doctorData.date_of_birth), + password_hash: hashedPassword, + role: Role.DOCTOR, + isVerified: false, + hasCompletedProfile: true, + }, + }); - await prisma.doctor.create({ - data: { - id: createdUser.id, - specialization: "IMMUNOLOGY", - account_status: DoctorAccountStatus.PENDING, - }, + await tx.doctor.create({ + data: { + id: createdUser.id, + specialization: "IMMUNOLOGY", + account_status: DoctorAccountStatus.PENDING, + }, + }); + return createdUser.id; }); + + // Upload files and update doctor record with files urls + console.log(doctorFiles); + + if (doctorFiles && Object.keys(doctorFiles).length > 0) { + const doctorFilesArray = Object.values(doctorFiles).flat() as Express.Multer.File[]; + console.log(doctorFilesArray); + + await this._uploadFiles(doctorFilesArray, createdUserId); + } } @@ -160,4 +176,83 @@ export class DoctorService { }); } + + private async _uploadFiles(files: Express.Multer.File[], doctorId: string): Promise { + const uploadedFiles: { public_id: string }[] = []; + + try { + // Validate all fieldnames before uploading + for (const file of files) { + if (!Object.values(DOCTOR_FILES).includes(file.fieldname as any)) { + const error = createBilingualError(400, ErrorMessages.UNKNOWN_FILE_FIELDNAME); + throw new HttpException(error.status, error.message, error.messageAr); + } + } + + // Upload all files to Cloudinary in parallel + const uploadResults = await Promise.all( + files.map(file => + cloudinary.uploader.upload(file.path, { + folder: `DOCTORS/documents/${doctorId}`, + overwrite: false, + public_id: `DOCTOR_${doctorId}_${file.originalname}_${Date.now()}` + }) + ) + ); + + // Track uploaded files for potential rollback + uploadedFiles.push(...uploadResults.map(r => ({ public_id: r.public_id }))); + + // Map file fields to database columns + const updateData: any = {}; + files.forEach((file, index) => { + const uploadResult = uploadResults[index]; + + switch (file.fieldname) { + case DOCTOR_FILES.GRADUATION_CERTIFICATE: + updateData.graduationCertificateUrl = uploadResult.secure_url; + updateData.graduationCertificatePublicId = uploadResult.public_id; + break; + case DOCTOR_FILES.MEMBERSHIP_CARD: + updateData.membershipCardUrl = uploadResult.secure_url; + updateData.membershipCardPublicId = uploadResult.public_id; + break; + case DOCTOR_FILES.PROFESSIONAL_PRACTICE_CARD: + updateData.professionalPracticeCardUrl = uploadResult.secure_url; + updateData.professionalPracticeCardPublicId = uploadResult.public_id; + break; + case DOCTOR_FILES.MASTERS_CERTIFICATE: + updateData.mastersCertificateUrl = uploadResult.secure_url; + updateData.mastersCertificatePublicId = uploadResult.public_id; + break; + case DOCTOR_FILES.FELLOWSHIP_CERTIFICATE: + updateData.fellowshipCertificateUrl = uploadResult.secure_url; + updateData.fellowshipCertificatePublicId = uploadResult.public_id; + break; + case DOCTOR_FILES.UNION_SPECIALIZATION_CERTIFICATE: + updateData.unionSpecializationCertificateUrl = uploadResult.secure_url; + updateData.unionSpecializationCertificatePublicId = uploadResult.public_id; + break; + } + fs.unlinkSync(file.path); // Delete local file after upload + }); + + // Update database with all URLs in a single operation + await prisma.doctor.update({ + where: { id: doctorId }, + data: updateData + }); + + } catch (error) { + // Rollback: Delete all uploaded files from Cloudinary + if (uploadedFiles.length > 0) { + await Promise.all( + uploadedFiles.map(f => cloudinary.uploader.destroy(f.public_id).catch(() => { })) + ); + + } + files.map(file => fs.unlinkSync(file.path)); // Delete local files in case of error + throw error; + } + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 155be25..17017d9 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -120,6 +120,10 @@ export const ErrorMessages = { en: 'No profile picture found', ar: 'لم يتم العثور على صورة الملف الشخصي', }, + UNKNOWN_FILE_FIELDNAME: { + en: 'Unknown file fieldname', + ar: 'اسم حقل الملف غير معروف', + }, // Clinic errors CLINIC_NOT_FOUND: { From 8e31413d9b38fdb42184d813a3de09810aa3c803 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 29 Jan 2026 19:30:56 +0200 Subject: [PATCH 093/210] Add certificate URLs to Doctor response DTO and retrieve unverified doctors endpoint and update Swagger documentation for file uploads --- src/dtos/admins.dto.ts | 6 ++ src/routes/admin.route.ts | 7 +- src/routes/doctors.route.ts | 85 ++++++++++++++++--- src/services/admin.service.ts | 8 +- src/services/doctor.service.ts | 6 +- src/swagger-output.json | 151 ++++++++++++++++++++++++--------- 6 files changed, 205 insertions(+), 58 deletions(-) diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index e5c2030..bd93f02 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -38,5 +38,11 @@ export class DoctorFromAdminResponseDto { specialization: string; avg_time?: Date; account_status?: DoctorAccountStatus; + fellowshipCertificateUrl?: string; + graduationCertificateUrl?: string; + mastersCertificateUrl?: string; + membershipCardUrl?: string; + unionSpecializationCertificateUrl?: string; + professionalPracticeCardUrl?: string; }; } \ No newline at end of file diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index f95b724..5ab7249 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -117,7 +117,12 @@ export class AdminRoute implements Routes { schema: { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, date_of_birth: '1990-01-01', photoUrl: null, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' } }], + doctor: { + specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED', + mastersCertificateUrl: '', graduationCertificateUrl: '', fellowshipCertificateUrl: '', professionalPracticeCardUrl: '', membershipCardUrl: '', unionSpecializationCertificateUrl: '' + } + } + ], messageEn: 'Unverified doctors retrieved successfully', messageAr: "تم استرجاع بيانات الأطباء غير المعتمدين بنجاح." } diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index f29ff52..a05da21 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,5 +1,5 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorLoginRequestDto, DoctorProfilePictureRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; @@ -24,18 +24,79 @@ export class DoctorsRoute implements Routes { `/doctors/signup`, /* #swagger.tags = ['Doctors'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Doctor signup data', + #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['email'] = { + in: 'formData', + description: 'Doctor email address', required: true, - schema: { - $email: 'doctor@example.com', - $name: 'Dr. Smith', - $phone: '1234567890', - $password: 'SecurePassword123', - $gender: 'MALE or FEMALE', - date_of_birth: '1990-01-01', - } + type: 'string' + } + #swagger.parameters['name'] = { + in: 'formData', + description: 'Doctor full name', + required: true, + type: 'string' + } + #swagger.parameters['phone'] = { + in: 'formData', + description: 'Doctor phone number', + required: true, + type: 'string' + } + #swagger.parameters['password'] = { + in: 'formData', + description: 'Doctor password', + required: true, + type: 'string' + } + #swagger.parameters['gender'] = { + in: 'formData', + description: 'Doctor gender (MALE or FEMALE)', + required: true, + type: 'string', + enum: ['MALE', 'FEMALE'] + } + #swagger.parameters['date_of_birth'] = { + in: 'formData', + description: 'Doctor date of birth (YYYY-MM-DD)', + required: true, + type: 'string' + } + #swagger.parameters['graduationCertificate'] = { + in: 'formData', + description: 'Graduation certificate PDF', + required: true, + type: 'file' + } + #swagger.parameters['membershipCard'] = { + in: 'formData', + description: 'Membership card PDF', + required: true, + type: 'file' + } + #swagger.parameters['professionalPracticeCard'] = { + in: 'formData', + description: 'Professional practice card PDF', + required: true, + type: 'file' + } + #swagger.parameters['mastersCertificate'] = { + in: 'formData', + description: 'Masters certificate PDF', + required: true, + type: 'file' + } + #swagger.parameters['fellowshipCertificate'] = { + in: 'formData', + description: 'Fellowship certificate PDF', + required: true, + type: 'file' + } + #swagger.parameters['unionSpecializationCertificate'] = { + in: 'formData', + description: 'Union specialization certificate PDF', + required: true, + type: 'file' } #swagger.responses[201] = { description: 'Doctor signup successful', diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 35e9c3d..872de14 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -171,7 +171,13 @@ export class AdminService { select: { specialization: true, avg_time: true, - account_status: true + account_status: true, + fellowshipCertificateUrl: true, + graduationCertificateUrl: true, + mastersCertificateUrl: true, + membershipCardUrl: true, + unionSpecializationCertificateUrl: true, + professionalPracticeCardUrl: true, } }, }, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index c4c21a5..9c8cf07 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -8,7 +8,6 @@ import { DoctorLoginData } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; import prisma from "@/config/prisma"; import cloudinary from "@/utils/cloudinary"; -import { UploadApiResponse } from "cloudinary"; import { DOCTOR_FILES } from "@/interfaces"; import fs from "fs"; @@ -71,12 +70,9 @@ export class DoctorService { return createdUser.id; }); - // Upload files and update doctor record with files urls - console.log(doctorFiles); - + // Upload files and update doctor record with files urls if (doctorFiles && Object.keys(doctorFiles).length > 0) { const doctorFilesArray = Object.values(doctorFiles).flat() as Express.Multer.File[]; - console.log(doctorFilesArray); await this._uploadFiles(doctorFilesArray, createdUserId); } diff --git a/src/swagger-output.json b/src/swagger-output.json index 750c022..3fa4415 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1507,6 +1507,30 @@ "account_status": { "type": "string", "example": "APPROVED" + }, + "mastersCertificateUrl": { + "type": "string", + "example": "" + }, + "graduationCertificateUrl": { + "type": "string", + "example": "" + }, + "fellowshipCertificateUrl": { + "type": "string", + "example": "" + }, + "professionalPracticeCardUrl": { + "type": "string", + "example": "" + }, + "membershipCardUrl": { + "type": "string", + "example": "" + }, + "unionSpecializationCertificateUrl": { + "type": "string", + "example": "" } } } @@ -2371,48 +2395,97 @@ "Doctors" ], "description": "", + "consumes": [ + "multipart/form-data" + ], "parameters": [ { - "name": "body", - "in": "body", - "description": "Doctor signup data", + "name": "email", + "in": "formData", + "description": "Doctor email address", "required": true, - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "doctor@example.com" - }, - "name": { - "type": "string", - "example": "Dr. Smith" - }, - "phone": { - "type": "string", - "example": "1234567890" - }, - "password": { - "type": "string", - "example": "SecurePassword123" - }, - "gender": { - "type": "string", - "example": "MALE or FEMALE" - }, - "date_of_birth": { - "type": "string", - "example": "1990-01-01" - } - }, - "required": [ - "email", - "name", - "phone", - "password", - "gender" - ] - } + "type": "string" + }, + { + "name": "name", + "in": "formData", + "description": "Doctor full name", + "required": true, + "type": "string" + }, + { + "name": "phone", + "in": "formData", + "description": "Doctor phone number", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "formData", + "description": "Doctor password", + "required": true, + "type": "string" + }, + { + "name": "gender", + "in": "formData", + "description": "Doctor gender (MALE or FEMALE)", + "required": true, + "type": "string", + "enum": [ + "MALE", + "FEMALE" + ] + }, + { + "name": "date_of_birth", + "in": "formData", + "description": "Doctor date of birth (YYYY-MM-DD)", + "required": true, + "type": "string" + }, + { + "name": "graduationCertificate", + "in": "formData", + "description": "Graduation certificate PDF", + "required": true, + "type": "file" + }, + { + "name": "membershipCard", + "in": "formData", + "description": "Membership card PDF", + "required": true, + "type": "file" + }, + { + "name": "professionalPracticeCard", + "in": "formData", + "description": "Professional practice card PDF", + "required": true, + "type": "file" + }, + { + "name": "mastersCertificate", + "in": "formData", + "description": "Masters certificate PDF", + "required": true, + "type": "file" + }, + { + "name": "fellowshipCertificate", + "in": "formData", + "description": "Fellowship certificate PDF", + "required": true, + "type": "file" + }, + { + "name": "unionSpecializationCertificate", + "in": "formData", + "description": "Union specialization certificate PDF", + "required": true, + "type": "file" } ], "responses": { From 61d567db5b3f8a805015e9b210073e3f3923fee3 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 29 Jan 2026 19:50:50 +0200 Subject: [PATCH 094/210] updated refresh token route --- src/routes/auth.route.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index aa138ba..e39bbfa 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -123,7 +123,6 @@ export class AuthRoute implements Routes { } } */ - AuthMiddleware, this.auth.refresh, ); From 9ac3c6488854186913fcf28ed32c9dc76590042e Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 29 Jan 2026 21:03:44 +0200 Subject: [PATCH 095/210] Add migration for new columns and types in Appointments and Doctor tables --- .../migration.sql | 71 +++++++++++++++++++ .../migration.sql | 9 +++ .../migration.sql | 54 ++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/prisma/migrations/20260125212701_appointments_modifications/migration.sql create mode 100644 src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql create mode 100644 src/prisma/migrations/20260129185802_merging_verify_files_with_dev/migration.sql diff --git a/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql b/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql new file mode 100644 index 0000000..c71c689 --- /dev/null +++ b/src/prisma/migrations/20260125212701_appointments_modifications/migration.sql @@ -0,0 +1,71 @@ +/* + Warnings: + + - Added the required column `end_time` to the `Appointments` table without a default value. This is not possible if the table is not empty. + - Added the required column `slot_duration` to the `Appointments` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "AvailabilityType" AS ENUM ('UNSET', 'ONLINE', 'OFFLINE', 'BOTH'); + +-- CreateEnum +CREATE TYPE "DayOfWeek" AS ENUM ('SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'); + +-- CreateEnum +CREATE TYPE "AppointmentStatus" AS ENUM ('CONFIRMED', 'COMPLETED', 'CANCELLED', 'NO_SHOW'); + +-- AlterTable +ALTER TABLE "Appointments" ADD COLUMN "cancelled_by" TEXT, +ADD COLUMN "clinic_id" TEXT, +ADD COLUMN "end_time" TIMESTAMP(3) NOT NULL, +ADD COLUMN "slot_duration" TIMESTAMP(3) NOT NULL, +ADD COLUMN "status" "AppointmentStatus" NOT NULL DEFAULT 'CONFIRMED'; + +-- AlterTable +ALTER TABLE "ClinicDoctor" ADD COLUMN "is_accepting" BOOLEAN NOT NULL DEFAULT true; + +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "availability_type" "AvailabilityType" NOT NULL DEFAULT 'UNSET', +ADD COLUMN "present" BOOLEAN NOT NULL DEFAULT true; + +-- CreateTable +CREATE TABLE "DoctorSchedules" ( + "id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "clinic_id" TEXT, + "day_of_week" "DayOfWeek" NOT NULL, + "start_time" TIME(0) NOT NULL, + "end_time" TIME(0) NOT NULL, + "slot_duration" INTEGER NOT NULL, + "buffer_time" INTEGER NOT NULL DEFAULT 0, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "DoctorSchedules_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_doctor_id_idx" ON "DoctorSchedules"("doctor_id"); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_clinic_id_idx" ON "DoctorSchedules"("clinic_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "DoctorSchedules_doctor_id_clinic_id_day_of_week_key" ON "DoctorSchedules"("doctor_id", "clinic_id", "day_of_week"); + +-- CreateIndex +CREATE INDEX "Appointments_clinic_id_idx" ON "Appointments"("clinic_id"); + +-- CreateIndex +CREATE INDEX "Appointments_status_idx" ON "Appointments"("status"); + +-- AddForeignKey +ALTER TABLE "Appointments" ADD CONSTRAINT "Appointments_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql b/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql new file mode 100644 index 0000000..50499f2 --- /dev/null +++ b/src/prisma/migrations/20260127172048_fix_slot_duration_column/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - Changed the type of `slot_duration` on the `Appointments` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- AlterTable +ALTER TABLE "Appointments" DROP COLUMN "slot_duration", +ADD COLUMN "slot_duration" INTEGER NOT NULL; diff --git a/src/prisma/migrations/20260129185802_merging_verify_files_with_dev/migration.sql b/src/prisma/migrations/20260129185802_merging_verify_files_with_dev/migration.sql new file mode 100644 index 0000000..ddffcd4 --- /dev/null +++ b/src/prisma/migrations/20260129185802_merging_verify_files_with_dev/migration.sql @@ -0,0 +1,54 @@ +/* + Warnings: + + - You are about to drop the column `cancelled_by` on the `Appointments` table. All the data in the column will be lost. + - You are about to drop the column `clinic_id` on the `Appointments` table. All the data in the column will be lost. + - You are about to drop the column `end_time` on the `Appointments` table. All the data in the column will be lost. + - You are about to drop the column `slot_duration` on the `Appointments` table. All the data in the column will be lost. + - You are about to drop the column `status` on the `Appointments` table. All the data in the column will be lost. + - You are about to drop the column `is_accepting` on the `ClinicDoctor` table. All the data in the column will be lost. + - You are about to drop the column `availability_type` on the `Doctor` table. All the data in the column will be lost. + - You are about to drop the column `present` on the `Doctor` table. All the data in the column will be lost. + - You are about to drop the `DoctorSchedules` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "public"."Appointments" DROP CONSTRAINT "Appointments_clinic_id_fkey"; + +-- DropForeignKey +ALTER TABLE "public"."DoctorSchedules" DROP CONSTRAINT "DoctorSchedules_clinic_id_fkey"; + +-- DropForeignKey +ALTER TABLE "public"."DoctorSchedules" DROP CONSTRAINT "DoctorSchedules_doctor_id_fkey"; + +-- DropIndex +DROP INDEX "public"."Appointments_clinic_id_idx"; + +-- DropIndex +DROP INDEX "public"."Appointments_status_idx"; + +-- AlterTable +ALTER TABLE "Appointments" DROP COLUMN "cancelled_by", +DROP COLUMN "clinic_id", +DROP COLUMN "end_time", +DROP COLUMN "slot_duration", +DROP COLUMN "status"; + +-- AlterTable +ALTER TABLE "ClinicDoctor" DROP COLUMN "is_accepting"; + +-- AlterTable +ALTER TABLE "Doctor" DROP COLUMN "availability_type", +DROP COLUMN "present"; + +-- DropTable +DROP TABLE "public"."DoctorSchedules"; + +-- DropEnum +DROP TYPE "public"."AppointmentStatus"; + +-- DropEnum +DROP TYPE "public"."AvailabilityType"; + +-- DropEnum +DROP TYPE "public"."DayOfWeek"; From 439da7d3a8cb06cd403e6cd3053d701ddf9db040 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 29 Jan 2026 21:34:24 +0200 Subject: [PATCH 096/210] reschedule all appointments of a specific day --- src/controllers/appointment.controller.ts | 20 ++++++ src/routes/appointment.route.ts | 57 +++++++++++++++- src/services/appointment.service.ts | 33 ++++++++- src/swagger-output.json | 81 +++++++++++++++++++++++ 4 files changed, 187 insertions(+), 4 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index f2ce7c6..a82f435 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -199,4 +199,24 @@ export class AppointmentController { message: 'Appointments rescheduled successfully', }); }); + + public rescheduleDayAppointments = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { currentDate, newDate, keepOriginalSlots } = req.body; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!currentDate || !newDate) { + const error = createBilingualError(400, ErrorMessages.DATE_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.rescheduleDayAppointments(doctorId, new Date(currentDate), new Date(newDate), keepOriginalSlots); + res.status(200).json({ + message: 'Appointments rescheduled successfully', + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index de6448b..076c908 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -593,7 +593,7 @@ export class AppointmentRoute implements Routes { (3) When using "newScheduledTime": \ - If "keepOriginalSlots" is true, appointments keep their original time-of-day but move to the new date. \ - If "keepOriginalSlots" is false, appointments are reallocated sequentially based on the doctor schedule.' - + #swagger.parameters['body'] = { in: 'body', description: 'Bulk reschedule parameters', @@ -635,6 +635,61 @@ export class AppointmentRoute implements Routes { this.appointmentController.bulkRescheduleByDoctor ); + this.router.patch( + `${this.path}/doctor/reschedule-day`, + /* + #swagger.path = '/appointments/doctor/reschedule-day' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + + #swagger.description = 'Reschedule all confirmed appointments of a specific day to a new date' + #swagger.parameters['body'] = { + in: 'body', + description: 'Parameters for moving all appointments from one day to another', + required: true, + schema: { + currentDate: '2026-02-10T09:00:00.000Z', + newDate: '2026-02-16T09:00:00.000Z', + keepOriginalSlots: true + } + } + + #swagger.responses[200] = { + description: 'All appointments for the day were successfully rescheduled', + schema: { + message: 'Appointments rescheduled successfully' + } + } + + #swagger.responses[400] = { + description: 'Bad request (missing/invalid dates, conflicting parameters, etc.)', + schema: { + message: 'Error message describing the issue (e.g. "Current date and new date cannot be the same", "Invalid date format", etc.)' + } + } + + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' + } + + #swagger.responses[403] = { + description: 'Forbidden - authenticated user is not a doctor or not authorized' + } + + #swagger.responses[404] = { + description: 'No confirmed appointments found for the specified current date' + } + */ + AuthMiddleware, + this.appointmentController.rescheduleDayAppointments + ) + } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index a007b20..b9a447c 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -6,7 +6,6 @@ import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { PatientAppointment } from '@/interfaces/appointments.interface'; -import { min } from 'class-validator'; @Service() export class AppointmentService { @@ -204,8 +203,6 @@ export class AppointmentService { estimated_time: schedule.slot_duration, } }); - - console.log('Appointment booked:', appointment); } public async getPatientAppointments(patientId: string): Promise { @@ -423,6 +420,36 @@ export class AppointmentService { } } + public async rescheduleDayAppointments(doctorId: string, currentDate: Date, newDate: Date, keepOriginalSlots: boolean): Promise { + + const startOfDay = new Date(currentDate); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(currentDate); + endOfDay.setHours(23, 59, 59, 999); + + const appointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay, + }, + status: { in: ['CONFIRMED'] }, + deleted_at: null, + }, + select: { + id: true, + scheduled_time: true, + }, + orderBy: { + scheduled_time: 'asc', + } + }); + + await this.bulkRescheduleByDoctor(doctorId, appointments.map(app => app.id), null, newDate, keepOriginalSlots); + } + public async cancelAppointment(userId: string, appointmentId: string): Promise { // see whether the user is patient or doctor const appointment = await prisma.appointment.findUnique({ diff --git a/src/swagger-output.json b/src/swagger-output.json index a74a2fa..a2bc871 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3942,6 +3942,87 @@ } } } + }, + "/appointments/doctor/reschedule-day": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Reschedule all confirmed appointments of a specific day to a new date", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Parameters for moving all appointments from one day to another", + "required": true, + "schema": { + "type": "object", + "properties": { + "currentDate": { + "type": "string", + "example": "2026-02-10T09:00:00.000Z" + }, + "newDate": { + "type": "string", + "example": "2026-02-16T09:00:00.000Z" + }, + "keepOriginalSlots": { + "type": "boolean", + "example": true + } + } + } + } + ], + "responses": { + "200": { + "description": "All appointments for the day were successfully rescheduled", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Appointments rescheduled successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request (missing/invalid dates, conflicting parameters, etc.)", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Error message describing the issue (e.g. \"Current date and new date cannot be the same\", \"Invalid date format\", etc.)" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized - user not authenticated" + }, + "403": { + "description": "Forbidden - authenticated user is not a doctor or not authorized" + }, + "404": { + "description": "No confirmed appointments found for the specified current date" + } + } + } } } } \ No newline at end of file From 56a97983f4eabe10b11fb4b77a0cd68814640dd6 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 30 Jan 2026 02:33:04 +0200 Subject: [PATCH 097/210] doctor's complete schedule --- src/controllers/appointment.controller.ts | 14 ++++ src/interfaces/appointments.interface.ts | 18 +++++ src/routes/appointment.route.ts | 75 ++++++++++++++++++ src/services/appointment.service.ts | 74 ++++++++++++++++- src/swagger-output.json | 96 +++++++++++++++++++++++ 5 files changed, 276 insertions(+), 1 deletion(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index a82f435..e53c1ee 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -219,4 +219,18 @@ export class AppointmentController { message: 'Appointments rescheduled successfully', }); }); + + public getDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + res.status(200).json({ + data: schedule, + }); + }); } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 24f5eca..abbbdf4 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -35,6 +35,24 @@ export interface PatientAppointment { clinic_address: string | null; } +export interface DoctorAppointment { + id: string; + status: AppointmentStatus; + slot_duration: number; + patient_name: string; + appointment_date: string; + start_time: string; + end_time: string; + clinic_name: string | null; + clinic_address: string | null; +} + +export interface DoctorScheduleDay { + date: string; + displayDate: string; + appointments: DoctorAppointment[]; +} + export interface AvailableDay { date: string; dayOfWeek: DayOfWeek; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 076c908..387a55d 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -690,6 +690,81 @@ export class AppointmentRoute implements Routes { this.appointmentController.rescheduleDayAppointments ) + this.router.get( + `${this.path}/doctor/schedule`, + /* + #swagger.path = '/appointments/doctor/schedule' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Get the doctor's complete schedule with all appointments grouped by date' + #swagger.responses[200] = { + description: 'Doctor schedule retrieved successfully', + schema: { + data: [ + { + date: '2026-02-03', + displayDate: 'Monday, February 3, 2026', + appointments: [ + { + id: 'appointment-uuid-1', + status: 'CONFIRMED', + slot_duration: 30, + patient_name: 'John Doe', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' + }, + { + id: 'appointment-uuid-2', + status: 'CONFIRMED', + slot_duration: 30, + patient_name: 'Jane Smith', + appointment_date: '2026-02-03', + start_time: '10:00', + end_time: '10:30', + clinic_name: null, + clinic_address: null + } + ] + }, + { + date: '2026-02-05', + displayDate: 'Wednesday, February 5, 2026', + appointments: [ + { + id: 'appointment-uuid-3', + status: 'CONFIRMED', + slot_duration: 45, + patient_name: 'Bob Johnson', + appointment_date: '2026-02-05', + start_time: '14:00', + end_time: '14:45', + clinic_name: 'Downtown Health Center', + clinic_address: '456 Oak Avenue' + } + ] + } + ] + } + } + #swagger.responses[400] = { + description: 'Bad request - doctor ID missing or invalid' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + */ + AuthMiddleware, + this.appointmentController.getDoctorSchedule + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index b9a447c..e3aa7c8 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientAppointment } from '@/interfaces/appointments.interface'; +import { DoctorAppointment, DoctorScheduleDay, PatientAppointment } from '@/interfaces/appointments.interface'; @Service() export class AppointmentService { @@ -493,6 +493,78 @@ export class AppointmentService { // penalty to be added later }; + public async getDoctorSchedule(doctorId: string) : Promise { + const now = new Date(); + + const appointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: new Date(), + }, + status: { in: ['CONFIRMED', 'COMPLETED'] }, + deleted_at: null, + }, + orderBy: { + scheduled_time: 'asc', + }, + select: { + id: true, + scheduled_time: true, + end_time: true, + slot_duration: true, + status: true, + clinic_id: true, + patient: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + } + }); + + const groupedByDate = new Map(); + + appointments.forEach(app => { + const dateKey = this.formatDate(app.scheduled_time); + + const doctorAppointment: DoctorAppointment = { + id: app.id, + status: app.status, + slot_duration: app.slot_duration, + patient_name: app.patient.name, + appointment_date: dateKey, + start_time: this.formatTime(app.scheduled_time), + end_time: this.formatTime(app.end_time), + clinic_name: app.clinic ? app.clinic.name : null, + clinic_address: app.clinic ? app.clinic.address : null, + }; + + if (!groupedByDate.has(dateKey)){ + groupedByDate.set(dateKey, []); + } + groupedByDate.get(dateKey).push(doctorAppointment); + }); + + const schedule: DoctorScheduleDay[] = []; + groupedByDate.forEach((appointments, dateKey) => { + const date = new Date(dateKey); + schedule.push({ + date: dateKey, + displayDate: this.formatDisplayDate(date), + appointments: appointments, + }); + }); + + return schedule; + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/swagger-output.json b/src/swagger-output.json index a2bc871..c3e0cbe 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4023,6 +4023,102 @@ } } } + }, + "/appointments/doctor/schedule": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get the doctor", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor schedule retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "example": "2026-02-05" + }, + "displayDate": { + "type": "string", + "example": "Wednesday, February 5, 2026" + }, + "appointments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid-3" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "slot_duration": { + "type": "number", + "example": 45 + }, + "patient_name": { + "type": "string", + "example": "Bob Johnson" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-05" + }, + "start_time": { + "type": "string", + "example": "14:00" + }, + "end_time": { + "type": "string", + "example": "14:45" + }, + "clinic_name": { + "type": "string", + "example": "Downtown Health Center" + }, + "clinic_address": { + "type": "string", + "example": "456 Oak Avenue" + } + } + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - doctor ID missing or invalid" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + } + } + } } } } \ No newline at end of file From cd7b25fbf17344d1e825cdd5ce8ca1413362fd95 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 30 Jan 2026 16:08:28 +0200 Subject: [PATCH 098/210] appointments modification: dto/routes --- src/controllers/appointment.controller.ts | 66 +++++------- src/dtos/appointments.dto.ts | 51 ++++++++- src/routes/appointment.route.ts | 125 ++++++++++------------ src/swagger-output.json | 125 ++++++++-------------- src/utils/responseMessages.ts | 37 +++++++ 5 files changed, 209 insertions(+), 195 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index e53c1ee..b639d09 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -5,6 +5,7 @@ import { catchAsync } from '@/utils/catchAsync'; import { AppointmentService } from "@/services/appointment.service" import Container from "typedi"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; export class AppointmentController { @@ -21,8 +22,10 @@ export class AppointmentController { } const availableDays = await this.appointmentService.getAvailableDays(doctorId, clinicId as string || null) + const response = createMultiLangMessage(SuccessResponseMessages.AVAILABLE_DAYS_RETRIEVED); res.status(200).json({ data: availableDays, + ...response }); }); @@ -55,8 +58,10 @@ export class AppointmentController { } const availableSlots = await this.appointmentService.getAvailableSlots(doctorId, clinicId as string || null, date as string) + const response = createMultiLangMessage(SuccessResponseMessages.AVAILABLE_SLOTS_RETRIEVED); res.status(200).json({ data: availableSlots, + ...response }); }); @@ -67,25 +72,16 @@ export class AppointmentController { const { doctorId, clinicId, scheduledTime } = req.body; const scheduledDate = new Date(scheduledTime); - if (!doctorId) { - const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - - if (!scheduledTime) { - const error = createBilingualError(400, ErrorMessages.SCHEDULED_TIME_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - if (isNaN(scheduledDate.getTime())) { const error = createBilingualError(400, ErrorMessages.INVALID_SCHEDULED_TIME); throw new HttpException(error.status, error.message, error.messageAr); } await this.appointmentService.bookAppointment(patientId, doctorId, clinicId || null, scheduledDate); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_BOOKED_SUCCESSFULLY); res.status(201).json({ - message: 'Appointment booked successfully', + ...response }); }); @@ -98,8 +94,10 @@ export class AppointmentController { } const appointments = await this.appointmentService.getPatientAppointments(patientId); + const response = createMultiLangMessage(SuccessResponseMessages.PATIENT_APPOINTMENTS_RETRIEVED); res.status(200).json({ data: appointments, + ...response }); }); @@ -113,8 +111,10 @@ export class AppointmentController { } const appointment = await this.appointmentService.getPatientSelectedAppointment(appointmentId, patientId); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_DETAILS_RETRIEVED); res.status(200).json({ data: appointment, + ...response }); }); @@ -122,20 +122,11 @@ export class AppointmentController { const patientId = req.user.id; const { appointmentId } = req.params; const { newScheduledTime } = req.body; - - if (!patientId) { - const error = createBilingualError(400, ErrorMessages.PATIENT_ID_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - - if (!newScheduledTime) { - const error = createBilingualError(400, ErrorMessages.SCHEDULED_TIME_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - + await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ - message: 'Appointment rescheduled successfully', + ...response }); }); @@ -144,8 +135,9 @@ export class AppointmentController { const { appointmentId } = req.params; await this.appointmentService.cancelAppointment(userId, appointmentId); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_CANCELLED_SUCCESSFULLY); res.status(200).json({ - message: 'Appointment cancelled successfully', + ...response }); }); @@ -159,19 +151,15 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - if (!minutes && !newScheduledTime) { - const error = createBilingualError(400, ErrorMessages.INVALID_RESCHEDULE_PARAMETERS); - throw new HttpException(error.status, error.message, error.messageAr); - } - if (minutes && newScheduledTime) { const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); throw new HttpException(error.status, error.message, error.messageAr); } await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ - message: 'Appointment rescheduled successfully', + ...response }); }); @@ -184,19 +172,15 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - if (!minutes && !newScheduledTime) { - const error = createBilingualError(400, ErrorMessages.INVALID_RESCHEDULE_PARAMETERS); - throw new HttpException(error.status, error.message, error.messageAr); - } - if (minutes && newScheduledTime) { const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); throw new HttpException(error.status, error.message, error.messageAr); } await this.appointmentService.bulkRescheduleByDoctor(doctorId, appointmentIds, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined, keepOriginalSlots); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ - message: 'Appointments rescheduled successfully', + ...response }); }); @@ -209,14 +193,10 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - if (!currentDate || !newDate) { - const error = createBilingualError(400, ErrorMessages.DATE_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - await this.appointmentService.rescheduleDayAppointments(doctorId, new Date(currentDate), new Date(newDate), keepOriginalSlots); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ - message: 'Appointments rescheduled successfully', + ...response }); }); @@ -229,8 +209,10 @@ export class AppointmentController { } const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); res.status(200).json({ data: schedule, + ...response }); }); } \ No newline at end of file diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index 6dddf85..9f9877a 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsNotEmpty, IsDateString, IsOptional, IsUUID } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsDateString, IsOptional, IsUUID, IsNumber, ValidateIf, IsArray } from 'class-validator'; export class BookAppointmentDto { @@ -38,4 +38,53 @@ export class GetAvailableSlotsDto { @IsUUID() @IsOptional() clinicId?: string; +} + +export class RescheduleAppointmentDto { + @IsDateString() + @IsNotEmpty() + newScheduledTime: string; +} + +export class RescheduleAppointmentByDoctorDto { + @IsNumber() + @IsOptional() + minutes?: number; + + @IsDateString() + @IsOptional() + newScheduledTime?: string; +} + +export class BulkRescheduleDto { + @IsArray() + @IsUUID('4', { each: true }) + @IsNotEmpty() + appointmentIds: string[]; + + @IsNumber() + @IsOptional() + minutes?: number; + + @IsDateString() + @IsOptional() + newScheduledTime?: string; + + @IsBoolean() + @IsOptional() + keepOriginalSlots?: boolean; +} + +export class RescheduleDayDto { + @IsDateString() + @IsNotEmpty() + currentDate: string; + + @IsDateString() + @IsNotEmpty() + newDate: string; + + @IsBoolean() + @IsOptional() + keepOriginalSlots?: boolean; } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 387a55d..80d49b8 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -5,7 +5,7 @@ import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import { BookAppointmentDto } from "@/dtos/appointments.dto"; +import { BookAppointmentDto, RescheduleAppointmentDto , RescheduleAppointmentByDoctorDto, BulkRescheduleDto, RescheduleDayDto} from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -298,24 +298,18 @@ export class AppointmentRoute implements Routes { ); this.router.get( - `${this.path}/patient/:patientId/appointments`, + `${this.path}/patient/appointments`, /* - #swagger.path = '/appointments/patient/{patientId}/appointments' + #swagger.path = '/appointments/patient/appointments' #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.description = 'Get all appointments for a specific patient. Note: clinic_name and clinic_address will be null for online appointments' - #swagger.parameters['patientId'] = { - in: 'path', - description: 'Patient ID', + description: 'Bearer token for authentication (must be a patient)', required: true, type: 'string' } + #swagger.description = 'Get all appointments for the patient' #swagger.responses[200] = { description: 'Patient appointments retrieved successfully', schema: { @@ -323,36 +317,33 @@ export class AppointmentRoute implements Routes { { id: 'appointment-uuid', status: 'CONFIRMED', - is_online: true, - slot_duration: 20, - doctor_name: 'House', + slot_duration: 30, + doctor_name: 'Dr. House', appointment_date: '2026-02-03', start_time: '09:00', - end_time: '09:20', - clinic_name: 'Medical Park Clinic', - clinic_address: '123 Main Street, New Cairo' + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' }, { - id: 'appointment-uuid-2', + id: 'appointment-uuid', status: 'CONFIRMED', - is_online: true, - slot_duration: 30, - doctor_name: 'Wilson', - appointment_date: '2026-02-05', - start_time: '14:00', - end_time: '14:30', - clinic_name: null, - clinic_address: null + slot_duration: 20, + doctor_name: 'Dr. House', + appointment_date: '2026-03-03', + start_time: '09:00', + end_time: '09:20', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' } - ], - message: 'Patient appointments retrieved successfully' + ] } } - #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' + #swagger.responses[400] = { + description: 'Bad request - patient ID missing' } - #swagger.responses[404] = { - description: 'Patient not found' + #swagger.responses[401] = { + description: 'Unauthorized - patient not authenticated' } */ AuthMiddleware, @@ -360,24 +351,18 @@ export class AppointmentRoute implements Routes { ); this.router.get( - `${this.path}/patient/:patientId/appointment/:appointmentId`, - /* - #swagger.path = '/appointments/patient/{patientId}/appointment/{appointmentId}' + `${this.path}/patient/:appointmentId`, + /* + #swagger.path = '/appointments/patient/{appointmentId}' #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.description = 'Get details of a specific appointment for a patient. Note: clinic_name and clinic_address will be null for online appointments' - #swagger.parameters['patientId'] = { - in: 'path', - description: 'Patient ID', + description: 'Bearer token for authentication (must be a patient)', required: true, type: 'string' } + #swagger.description = 'Get details of a specific appointment for the patient' #swagger.parameters['appointmentId'] = { in: 'path', description: 'Appointment ID', @@ -390,23 +375,24 @@ export class AppointmentRoute implements Routes { data: { id: 'appointment-uuid', status: 'CONFIRMED', - is_online: true, - slot_duration: 20, - doctor_name: 'House', + slot_duration: 30, + doctor_name: 'Dr. House', appointment_date: '2026-02-03', start_time: '09:00', - end_time: '09:20', - clinic_name: 'Medical Park Clinic', - clinic_address: '123 Main Street, New Cairo' - }, - message: 'Appointment details retrieved successfully' + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' + } } } + #swagger.responses[400] = { + description: 'Bad request - patient ID missing' + } #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' + description: 'Unauthorized - patient not authenticated' } #swagger.responses[404] = { - description: 'Appointment not found' + description: 'Appointment not found or does not belong to the patient' } */ AuthMiddleware, @@ -414,24 +400,18 @@ export class AppointmentRoute implements Routes { ); this.router.patch( - `${this.path}/patient/:patientId/appointment/:appointmentId/reschedule`, + `${this.path}/patient/:appointmentId/reschedule`, /* - #swagger.path = '/appointments/patient/{patientId}/appointment/{appointmentId}/reschedule' + #swagger.path = '/appointments/patient/{appointmentId}/reschedule' #swagger.method = 'patch' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - #swagger.description = 'Reschedule an existing appointment to a new time slot' - #swagger.parameters['patientId'] = { - in: 'path', - description: 'Patient ID', + description: 'Bearer token for authentication (must be a patient)', required: true, type: 'string' } + #swagger.description = 'Reschedule an appointment to a new time by the patient' #swagger.parameters['appointmentId'] = { in: 'path', description: 'Appointment ID to reschedule', @@ -443,7 +423,7 @@ export class AppointmentRoute implements Routes { description: 'New scheduled time for the appointment', required: true, schema: { - newScheduledTime: '2026-02-05T11:00:00.000Z' + newScheduledTime: '2026-02-10T10:30:00.000Z' } } #swagger.responses[200] = { @@ -453,20 +433,20 @@ export class AppointmentRoute implements Routes { } } #swagger.responses[400] = { - description: 'Bad request - invalid time or slot not available', - schema: { - message: 'Error message describing the issue' - } + description: 'Bad request - missing or invalid parameters (patient ID, new scheduled time, etc.)' } #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' + description: 'Unauthorized - patient not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - appointment does not belong to the authenticated patient' } #swagger.responses[404] = { - description: 'Appointment not found' + description: 'Appointment not found or time slot not available' } */ - AuthMiddleware, + ValidationMiddleware(RescheduleAppointmentDto), this.appointmentController.rescheduleAppointmentByPatient ); @@ -571,6 +551,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, + ValidationMiddleware(RescheduleAppointmentByDoctorDto), this.appointmentController.rescheduleAppointmentByDoctor ); @@ -632,6 +613,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, + ValidationMiddleware(BulkRescheduleDto), this.appointmentController.bulkRescheduleByDoctor ); @@ -687,6 +669,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, + ValidationMiddleware(RescheduleDayDto), this.appointmentController.rescheduleDayAppointments ) diff --git a/src/swagger-output.json b/src/swagger-output.json index c3e0cbe..3e84ea6 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3413,24 +3413,17 @@ ] } }, - "/appointments/patient/{patientId}/appointments": { + "/appointments/patient/appointments": { "get": { "tags": [ "Appointments" ], - "description": "Get all appointments for a specific patient. Note: clinic_name and clinic_address will be null for online appointments", + "description": "Get all appointments for the patient", "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "Patient ID" - }, { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication", + "description": "Bearer token for authentication (must be a patient)", "required": true, "type": "string" } @@ -3448,44 +3441,42 @@ "properties": { "id": { "type": "string", - "example": "appointment-uuid-2" + "example": "appointment-uuid" }, "status": { "type": "string", "example": "CONFIRMED" }, - "is_online": { - "type": "boolean", - "example": true - }, "slot_duration": { "type": "number", - "example": 30 + "example": 20 }, "doctor_name": { "type": "string", - "example": "Wilson" + "example": "Dr. House" }, "appointment_date": { "type": "string", - "example": "2026-02-05" + "example": "2026-03-03" }, "start_time": { "type": "string", - "example": "14:00" + "example": "09:00" }, "end_time": { "type": "string", - "example": "14:30" + "example": "09:20" + }, + "clinic_name": { + "type": "string", + "example": "New Cairo Medical Clinic" }, - "clinic_name": {}, - "clinic_address": {} + "clinic_address": { + "type": "string", + "example": "123 Main Street, Medical Park" + } } } - }, - "message": { - "type": "string", - "example": "Patient appointments retrieved successfully" } }, "xml": { @@ -3493,29 +3484,22 @@ } } }, - "401": { - "description": "Unauthorized - user not authenticated" + "400": { + "description": "Bad request - patient ID missing" }, - "404": { - "description": "Patient not found" + "401": { + "description": "Unauthorized - patient not authenticated" } } } }, - "/appointments/patient/{patientId}/appointment/{appointmentId}": { + "/appointments/patient/{appointmentId}": { "get": { "tags": [ "Appointments" ], - "description": "Get details of a specific appointment for a patient. Note: clinic_name and clinic_address will be null for online appointments", + "description": "Get details of a specific appointment for the patient", "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "Patient ID" - }, { "name": "appointmentId", "in": "path", @@ -3526,7 +3510,7 @@ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication", + "description": "Bearer token for authentication (must be a patient)", "required": true, "type": "string" } @@ -3548,17 +3532,13 @@ "type": "string", "example": "CONFIRMED" }, - "is_online": { - "type": "boolean", - "example": true - }, "slot_duration": { "type": "number", - "example": 20 + "example": 30 }, "doctor_name": { "type": "string", - "example": "House" + "example": "Dr. House" }, "appointment_date": { "type": "string", @@ -3570,21 +3550,17 @@ }, "end_time": { "type": "string", - "example": "09:20" + "example": "09:30" }, "clinic_name": { "type": "string", - "example": "Medical Park Clinic" + "example": "New Cairo Medical Clinic" }, "clinic_address": { "type": "string", - "example": "123 Main Street, New Cairo" + "example": "123 Main Street, Medical Park" } } - }, - "message": { - "type": "string", - "example": "Appointment details retrieved successfully" } }, "xml": { @@ -3592,29 +3568,25 @@ } } }, + "400": { + "description": "Bad request - patient ID missing" + }, "401": { - "description": "Unauthorized - user not authenticated" + "description": "Unauthorized - patient not authenticated" }, "404": { - "description": "Appointment not found" + "description": "Appointment not found or does not belong to the patient" } } } }, - "/appointments/patient/{patientId}/appointment/{appointmentId}/reschedule": { + "/appointments/patient/{appointmentId}/reschedule": { "patch": { "tags": [ "Appointments" ], - "description": "Reschedule an existing appointment to a new time slot", + "description": "Reschedule an appointment to a new time by the patient", "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "Patient ID" - }, { "name": "appointmentId", "in": "path", @@ -3625,7 +3597,7 @@ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication", + "description": "Bearer token for authentication (must be a patient)", "required": true, "type": "string" }, @@ -3639,7 +3611,7 @@ "properties": { "newScheduledTime": { "type": "string", - "example": "2026-02-05T11:00:00.000Z" + "example": "2026-02-10T10:30:00.000Z" } } } @@ -3662,25 +3634,16 @@ } }, "400": { - "description": "Bad request - invalid time or slot not available", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Error message describing the issue" - } - }, - "xml": { - "name": "main" - } - } + "description": "Bad request - missing or invalid parameters (patient ID, new scheduled time, etc.)" }, "401": { - "description": "Unauthorized - user not authenticated" + "description": "Unauthorized - patient not authenticated" + }, + "403": { + "description": "Forbidden - appointment does not belong to the authenticated patient" }, "404": { - "description": "Appointment not found" + "description": "Appointment not found or time slot not available" } } } diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 32c6217..ee3e2c3 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -90,6 +90,10 @@ export const SuccessResponseMessages = { message_en: "Password set successfully.", message_ar: "تم تعيين كلمة المرور بنجاح.", }, + DOCTOR_SCHEDULE_RETRIEVED: { + message_en: "Doctor schedule retrieved successfully.", + message_ar: "تم استرجاع جدول الطبيب بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { @@ -129,6 +133,39 @@ export const SuccessResponseMessages = { message_ar: "تم حذف صورة الملف الشخصي بنجاح.", }, + // success messages for appointments + APPOINTMENT_BOOKED_SUCCESSFULLY: { + message_en: "Appointment booked successfully.", + message_ar: "تم حجز الموعد بنجاح.", + }, + APPOINTMENT_RESCHEDULED_SUCCESSFULLY: { + message_en: "Appointment rescheduled successfully.", + message_ar: "تم إعادة جدولة الموعد بنجاح.", + }, + APPOINTMENTS_RESCHEDULED_SUCCESSFULLY: { + message_en: "Appointments rescheduled successfully.", + message_ar: "تم إعادة جدولة المواعيد بنجاح.", + }, + APPOINTMENT_CANCELLED_SUCCESSFULLY: { + message_en: "Appointment cancelled successfully.", + message_ar: "تم إلغاء الموعد بنجاح.", + }, + AVAILABLE_DAYS_RETRIEVED: { + message_en: "Available days retrieved successfully.", + message_ar: "تم استرجاع الأيام المتاحة بنجاح.", + }, + AVAILABLE_SLOTS_RETRIEVED: { + message_en: "Available slots retrieved successfully.", + message_ar: "تم استرجاع الأوقات المتاحة بنجاح.", + }, + PATIENT_APPOINTMENTS_RETRIEVED: { + message_en: "Patient appointments retrieved successfully.", + message_ar: "تم استرجاع مواعيد المريض بنجاح.", + }, + APPOINTMENT_DETAILS_RETRIEVED: { + message_en: "Appointment details retrieved successfully.", + message_ar: "تم استرجاع تفاصيل الموعد بنجاح.", + }, } interface MultiLangMessageObj { From 573194bda4924c36ce6f0dd199e797040233b90c Mon Sep 17 00:00:00 2001 From: kareem Date: Fri, 30 Jan 2026 18:50:48 +0200 Subject: [PATCH 099/210] Fix refresh token issue --- src/controllers/auth.controller.ts | 4 +++- src/services/auth.service.ts | 34 +++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 953414c..6ab33d8 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -59,7 +59,9 @@ export class AuthController { const refreshToken = req.cookies?.RefreshToken; const { cookies, user, accessToken } = await this.auth.refreshAccessToken(refreshToken); - res.setHeader('Set-Cookie', cookies); + cookies.forEach((cookie: string) => { + res.append('Set-Cookie', cookie); + }); const responseMessage = createMultiLangMessage(SuccessResponseMessages.TOKEN_REFRESHED_SUCCESSFULLY); res.status(200).json({ data: { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 070f314..1391ec9 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -198,7 +198,14 @@ export class AuthService { // Verify the refresh token const secretKey: string = REFRESH_TOKEN_SECRET; - const decoded = verify(refreshToken, secretKey) as DataStoredInToken; + let decoded: DataStoredInToken; + + try { + decoded = verify(refreshToken, secretKey) as DataStoredInToken; + } catch (error) { + const err = createBilingualError(401, ErrorMessages.INVALID_REFRESH_TOKEN); + throw new HttpException(err.status, err.message, err.messageAr); + } // Hash the token to compare with stored hash const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex'); @@ -219,15 +226,36 @@ export class AuthService { } // Get user - const user = await this.users.findUnique({ where: { id: decoded.id } }); + const user = await this.users.findUnique({ + where: { id: decoded.id }, + include: { doctor: true } // Include doctor relation if needed + }); + if (!user) { const error = createBilingualError(401, ErrorMessages.USER_NOT_EXIST); throw new HttpException(error.status, error.message, error.messageAr); } + // Revoke the old refresh token (token rotation for security) + await this.refreshTokens.update({ + where: { id: storedToken.id }, + data: { + is_revoked: true, + revoked_at: new Date() + } + }); + // Create new access token const accessToken = this.createAccessToken(user); - const cookies = this.createCookies({ accessToken }); + + // Create new refresh token (token rotation) + const newRefreshToken = await this.createRefreshToken(user); + + // Create cookies with both tokens + const cookies = this.createCookies({ + accessToken, + refreshToken: newRefreshToken + }); return { cookies, user, accessToken }; } From b968b51559c9d3dc4d7d71aa1b5e6fe389d97587 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 30 Jan 2026 19:14:19 +0200 Subject: [PATCH 100/210] fixed errors from frontend --- src/controllers/admin.controller.ts | 1 + src/dtos/users.dto.ts | 5 ++++- src/middlewares/multer.middleware.ts | 2 +- src/services/admin.service.ts | 28 +++++++++++++++++++++++++++- src/services/auth.service.ts | 2 +- src/services/doctor.service.ts | 2 +- 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 0613d17..4f10e8f 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -114,6 +114,7 @@ export class AdminController { const doctorId = req.params.id; const { isVerified } = req.body; await this.adminService.updateDoctorVerificationStatus(doctorId, isVerified); + await this.adminService.sendVerificationStatusEmail(doctorId, isVerified); const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_VERIFICATION_STATUS_UPDATED); res.status(200).json({ messageEn: responseMessage.messageEn, diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 2b1629b..9a0f4c1 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -1,5 +1,5 @@ import { Gender } from '@prisma/client'; -import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDateString, IsBoolean, IsOptional } from 'class-validator'; +import { IsEmail, IsString, IsNotEmpty, MinLength, MaxLength, IsDateString, IsBoolean } from 'class-validator'; export class CreateUserDto { @IsEmail() @@ -18,6 +18,9 @@ export class CreateUserDto { @MinLength(8) @MaxLength(32) public password: string; + + @IsBoolean() + public rememberMe: boolean; } export class LoginUserDto { diff --git a/src/middlewares/multer.middleware.ts b/src/middlewares/multer.middleware.ts index e9a0c8b..cf312e5 100644 --- a/src/middlewares/multer.middleware.ts +++ b/src/middlewares/multer.middleware.ts @@ -11,7 +11,7 @@ const storage = multer.diskStorage({ }, filename: (req: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => { // We create a unique name: "doctor-timestamp.jpg" - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + const uniqueSuffix = Math.round(Math.random() * 1E9); cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); } }); diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 872de14..6af9047 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -5,6 +5,8 @@ import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto } from '@/dtos/admins import { HttpException } from '@/exceptions/HttpException'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import { User } from '@/interfaces'; +import { SENDER_EMAIL } from '@/config'; +import { transporter } from '@/utils/nodeMailerService'; // TO BE CHANGED const prisma = new PrismaClient(); @@ -205,4 +207,28 @@ export class AdminService { } }); } -} + + public async sendVerificationStatusEmail(doctorId: string, isApproved: boolean): Promise { + const doctor = await prisma.user.findUnique({ + where: { id: doctorId, role: Role.DOCTOR }, + select: { email: true, name: true } + }); + if (!doctor) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const mailOptions = { + from: SENDER_EMAIL, + to: doctor.email, + subject: isApproved ? 'Doctor Account Approved - MedBridge' : 'Doctor Account Rejected - MedBridge', + html: ` +

Dear Dr. ${doctor.name},

+

Your account has been ${isApproved ? 'approved' : 'rejected'}.

+

Thank you for using our platform.

+

Best regards,
MedicBridge Team

+ ` + }; + + await transporter.sendMail(mailOptions); + } +} \ No newline at end of file diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 070f314..f3a8a23 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -51,7 +51,7 @@ export class AuthService { } }); - const tokenResponse = await this.createTokens(createdUserData, true); + const tokenResponse = await this.createTokens(createdUserData, userData.rememberMe); const cookies = this.createCookies(tokenResponse); return { createdUserData, cookies }; diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 9c8cf07..f4de84c 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -191,7 +191,7 @@ export class DoctorService { cloudinary.uploader.upload(file.path, { folder: `DOCTORS/documents/${doctorId}`, overwrite: false, - public_id: `DOCTOR_${doctorId}_${file.originalname}_${Date.now()}` + public_id: `DOCTOR_${doctorId}_${file.fieldname}_${Date.now()}` }) ) ); From c42bf20ed807abdfd85c4807c6b44334817aaa39 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 30 Jan 2026 19:29:11 +0200 Subject: [PATCH 101/210] updated multer config for FE --- src/middlewares/multer.middleware.ts | 2 +- src/services/doctor.service.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/middlewares/multer.middleware.ts b/src/middlewares/multer.middleware.ts index cf312e5..120c224 100644 --- a/src/middlewares/multer.middleware.ts +++ b/src/middlewares/multer.middleware.ts @@ -12,7 +12,7 @@ const storage = multer.diskStorage({ filename: (req: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => { // We create a unique name: "doctor-timestamp.jpg" const uniqueSuffix = Math.round(Math.random() * 1E9); - cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); + cb(null, file.fieldname + path.extname(file.originalname)); } }); diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index f4de84c..90dd2ea 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -230,6 +230,8 @@ export class DoctorService { updateData.unionSpecializationCertificatePublicId = uploadResult.public_id; break; } + console.log(`Deleting ${file.path}`); + fs.unlinkSync(file.path); // Delete local file after upload }); @@ -247,6 +249,7 @@ export class DoctorService { ); } + files.map(file => fs.unlinkSync(file.path)); // Delete local files in case of error throw error; } From 1b397d1f57db52d3d4dd14dfa7aa916028fd9b6b Mon Sep 17 00:00:00 2001 From: kareem Date: Fri, 30 Jan 2026 19:45:29 +0200 Subject: [PATCH 102/210] enhance error handling for file deletion in DoctorService --- package-lock.json | 1 - src/services/doctor.service.ts | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df2d0cc..d7f5a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,6 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", - "multer": "^2.0.2", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "swagger-autogen": "^2.23.7", diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 90dd2ea..d706157 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -250,7 +250,12 @@ export class DoctorService { } - files.map(file => fs.unlinkSync(file.path)); // Delete local files in case of error + // Delete local files in case of error (only if they still exist) + files.forEach(file => { + if (fs.existsSync(file.path)) { + fs.unlinkSync(file.path); + } + }); throw error; } } From 1fd3f5f7550e14db1173793a073cf4a8b0639249 Mon Sep 17 00:00:00 2001 From: salahmohamed Date: Fri, 30 Jan 2026 23:32:09 +0200 Subject: [PATCH 103/210] Refactor updateDoctorVerificationStatus to handle null approval status --- src/services/admin.service.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 35e9c3d..d97fa38 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -179,7 +179,7 @@ export class AdminService { return unverifiedDoctors } - public async updateDoctorVerificationStatus(doctorId: string, isApproved: boolean): Promise { + public async updateDoctorVerificationStatus(doctorId: string, isApproved: boolean | null): Promise { const doctor = await prisma.user.findUnique({ where: { id: doctorId, role: Role.DOCTOR }, @@ -188,12 +188,22 @@ export class AdminService { const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } + + let accountStatus: DoctorAccountStatus; + if (isApproved === true) { + accountStatus = DoctorAccountStatus.APPROVED; + } else if (isApproved === false) { + accountStatus = DoctorAccountStatus.REJECTED; + } else { + accountStatus = DoctorAccountStatus.PENDING; + } + await prisma.user.update({ where: { id: doctorId }, data: { doctor: { update: { - account_status: isApproved ? DoctorAccountStatus.APPROVED : DoctorAccountStatus.REJECTED, + account_status: accountStatus, } } } From 7c6af3cfaae53f10cddf38923f117354fcd38e6b Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 30 Jan 2026 23:44:12 +0200 Subject: [PATCH 104/210] changed isVerified status in doctor signup to always true --- src/services/doctor.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index d706157..cb119dd 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -55,7 +55,7 @@ export class DoctorService { date_of_birth: new Date(doctorData.date_of_birth), password_hash: hashedPassword, role: Role.DOCTOR, - isVerified: false, + isVerified: true, hasCompletedProfile: true, }, }); From 6d2f890910b77f66edbee9cb8809078720d53dd7 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 31 Jan 2026 02:08:50 +0200 Subject: [PATCH 105/210] socket service --- package-lock.json | 226 +++++++++++++++++++++- package.json | 2 + src/controllers/appointment.controller.ts | 11 +- src/controllers/queue.controller.ts | 0 src/routes/appointment.route.ts | 37 ++-- src/routes/queue.route.ts | 0 src/services/appointment.service.ts | 106 +++++----- src/services/queue.service.ts | 0 src/services/socket.service.ts | 90 +++++++++ src/swagger-output.json | 24 ++- 10 files changed, 418 insertions(+), 78 deletions(-) create mode 100644 src/controllers/queue.controller.ts create mode 100644 src/routes/queue.route.ts create mode 100644 src/services/queue.service.ts create mode 100644 src/services/socket.service.ts diff --git a/package-lock.json b/package-lock.json index 718daed..a41ff00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "passport-google-oauth20": "^2.0.0", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", + "socket.io": "^4.8.3", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -57,6 +58,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", + "@types/socket.io": "^3.0.1", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", @@ -3631,6 +3633,12 @@ "text-hex": "1.0.x" } }, + "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==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -3878,7 +3886,6 @@ }, "node_modules/@types/cors": { "version": "2.8.19", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4115,6 +4122,16 @@ "@types/node": "*" } }, + "node_modules/@types/socket.io": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.1.tgz", + "integrity": "sha512-XSma2FhVD78ymvoxYV4xGXrIH/0EKQ93rR+YR0Y+Kw1xbPzLDCip/UWSejZ08FpxYeYNci/PZPQS9anrvJRqMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "socket.io": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "dev": true, @@ -4929,6 +4946,15 @@ ], "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" + } + }, "node_modules/base64url": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", @@ -6269,6 +6295,99 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@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.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/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" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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/enquirer": { "version": "2.3.6", "dev": true, @@ -12142,6 +12261,111 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "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.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io/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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/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" + } + }, "node_modules/socks": { "version": "2.8.7", "dev": true, diff --git a/package.json b/package.json index d7469f6..a6823ef 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "passport-google-oauth20": "^2.0.0", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", + "socket.io": "^4.8.3", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -72,6 +73,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", + "@types/socket.io": "^3.0.1", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index b639d09..faabfb3 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -122,7 +122,7 @@ export class AppointmentController { const patientId = req.user.id; const { appointmentId } = req.params; const { newScheduledTime } = req.body; - + await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ @@ -186,14 +186,19 @@ export class AppointmentController { public rescheduleDayAppointments = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; - const { currentDate, newDate, keepOriginalSlots } = req.body; + const { currentDate, minutes, newDate, keepOriginalSlots } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); throw new HttpException(error.status, error.message, error.messageAr); } - await this.appointmentService.rescheduleDayAppointments(doctorId, new Date(currentDate), new Date(newDate), keepOriginalSlots); + if (minutes && newDate) { + const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.rescheduleDayAppointments(doctorId, new Date(currentDate), minutes, newDate ? new Date(newDate) : undefined, keepOriginalSlots); const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response diff --git a/src/controllers/queue.controller.ts b/src/controllers/queue.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 80d49b8..f37a7ab 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -5,7 +5,7 @@ import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import { BookAppointmentDto, RescheduleAppointmentDto , RescheduleAppointmentByDoctorDto, BulkRescheduleDto, RescheduleDayDto} from "@/dtos/appointments.dto"; +import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, BulkRescheduleDto, RescheduleDayDto } from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -553,7 +553,7 @@ export class AppointmentRoute implements Routes { AuthMiddleware, ValidationMiddleware(RescheduleAppointmentByDoctorDto), this.appointmentController.rescheduleAppointmentByDoctor - ); + ); this.router.patch( `${this.path}/doctor/bulk-reschedule`, @@ -625,47 +625,49 @@ export class AppointmentRoute implements Routes { #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication (must be a doctor)', + description: 'Bearer token for authentication (doctor)', required: true, type: 'string' } + #swagger.description = 'Reschedule all appointments on a specific day by the authenticated doctor. \ + Rules: \ + (1) You must provide EITHER "minutes" OR "newDate" (not both). \ + (2) When using "minutes", all appointments on the specified day are shifted by the same number of minutes. \ + (3) When using "newDate": \ + - If "keepOriginalSlots" is true, appointments keep their original time-of-day but move to the new date. \ + - If "keepOriginalSlots" is false, appointments are reallocated sequentially based on the doctor schedule.' - #swagger.description = 'Reschedule all confirmed appointments of a specific day to a new date' #swagger.parameters['body'] = { in: 'body', - description: 'Parameters for moving all appointments from one day to another', + description: 'Reschedule day parameters', required: true, schema: { currentDate: '2026-02-10T09:00:00.000Z', - newDate: '2026-02-16T09:00:00.000Z', + minutes: 15, + newDate: '2026-02-13T09:00:00.000Z', keepOriginalSlots: true } } - #swagger.responses[200] = { - description: 'All appointments for the day were successfully rescheduled', + description: 'Appointments rescheduled successfully', schema: { message: 'Appointments rescheduled successfully' } } - #swagger.responses[400] = { - description: 'Bad request (missing/invalid dates, conflicting parameters, etc.)', + description: 'Bad request - invalid or conflicting reschedule parameters', schema: { - message: 'Error message describing the issue (e.g. "Current date and new date cannot be the same", "Invalid date format", etc.)' + message: 'Error message describing the issue' } } - #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' + description: 'Unauthorized - doctor not authenticated' } - #swagger.responses[403] = { - description: 'Forbidden - authenticated user is not a doctor or not authorized' + description: 'Forbidden - one or more appointments do not belong to the authenticated doctor' } - #swagger.responses[404] = { - description: 'No confirmed appointments found for the specified current date' + description: 'No appointments found on the specified day' } */ AuthMiddleware, @@ -750,4 +752,3 @@ export class AppointmentRoute implements Routes { ); } } - diff --git a/src/routes/queue.route.ts b/src/routes/queue.route.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index e3aa7c8..a0c6f03 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1,7 +1,7 @@ import prisma from '@/config/prisma'; import { DayOfWeek } from '@prisma/client'; import { AvailableDay } from '@/interfaces'; -import { Service } from 'typedi'; +import { Service } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; @@ -10,7 +10,7 @@ import { DoctorAppointment, DoctorScheduleDay, PatientAppointment } from '@/inte @Service() export class AppointmentService { - public async getAvailableDays(doctorId: string, clinicId: string | null): Promise{ + public async getAvailableDays(doctorId: string, clinicId: string | null): Promise { const daysAhead = 30 const availableDays: AvailableDay[] = []; const isOnline = await this.doctorIsOnline(doctorId); @@ -21,13 +21,13 @@ export class AppointmentService { } const schedules = await prisma.doctorSchedule.findMany({ - where:{ + where: { doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, is_active: true, deleted_at: null }, - select:{ + select: { day_of_week: true, start_time: true, end_time: true, @@ -36,7 +36,7 @@ export class AppointmentService { } }); - if (schedules.length === 0){ + if (schedules.length === 0) { return []; } @@ -53,9 +53,9 @@ export class AppointmentService { }); const today = new Date(); - today.setHours(0,0,0,0); + today.setHours(0, 0, 0, 0); - for (let i=1; i<= daysAhead; i++){ + for (let i = 1; i <= daysAhead; i++) { // create a copy from today --> if we used today directly it will be modified to today + 1 --> tomorrow date const currentDate = new Date(today); currentDate.setDate(today.getDate() + i); // current day now is = today + 1 @@ -64,13 +64,13 @@ export class AppointmentService { const schedule = scheduleMap.get(dayOfWeek); // skip if doctor doesnt work on this day - if (!schedule){ + if (!schedule) { continue; } const hasAvailableSlots = true; - if (hasAvailableSlots){ + if (hasAvailableSlots) { availableDays.push({ date: this.formatDate(currentDate), dayOfWeek: dayOfWeek, @@ -81,7 +81,7 @@ export class AppointmentService { return availableDays; } - public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise[]>{ + public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise[]> { const requestedDate = new Date(date); const dayOfWeek = this.getDayOfWeek(requestedDate.getDay()); const isOnline = await this.doctorIsOnline(doctorId); @@ -95,21 +95,21 @@ export class AppointmentService { today.setHours(0, 0, 0, 0); const requestedDateOnly = new Date(requestedDate); requestedDateOnly.setHours(0, 0, 0, 0); - + if (requestedDateOnly < today) { const error = createBilingualError(400, ErrorMessages.APPOINTMENT_IN_PAST); throw new HttpException(error.status, error.message, error.messageAr); } const schedule = await prisma.doctorSchedule.findFirst({ - where:{ + where: { day_of_week: dayOfWeek, doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, is_active: true, deleted_at: null, }, - select:{ + select: { start_time: true, end_time: true, slot_duration: true, @@ -150,7 +150,7 @@ export class AppointmentService { const availableSlots = allSlots.filter(slot => { const slotStart = this.parseTimeToDate(requestedDate, slot.start); - const slotEnd = this.parseTimeToDate(requestedDate, slot.end); + const slotEnd = this.parseTimeToDate(requestedDate, slot.end); const isBooked = existingAppointments.some(appointment => { const appointmentStart = new Date(appointment.scheduled_time); @@ -169,7 +169,7 @@ export class AppointmentService { return availableSlots; } - public async bookAppointment(patientId: string, doctorId: string, clinicId: string | null, scheduledTime: Date): Promise{ + public async bookAppointment(patientId: string, doctorId: string, clinicId: string | null, scheduledTime: Date): Promise { const isOnline = await this.doctorIsOnline(doctorId); if (!isOnline && !clinicId) { const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); @@ -177,30 +177,30 @@ export class AppointmentService { } const schedule = await prisma.doctorSchedule.findFirst({ - where:{ + where: { doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, is_active: true, deleted_at: null, day_of_week: this.getDayOfWeek(scheduledTime.getDay()), }, - select:{ + select: { slot_duration: true, } }); - const endTime = new Date(scheduledTime.getTime() + schedule.slot_duration * 60000); + const endTime = new Date(scheduledTime.getTime() + schedule.slot_duration * 60000); const appointment = await prisma.appointment.create({ - data:{ + data: { patient_id: patientId, doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, scheduled_time: scheduledTime, - slot_duration: schedule.slot_duration, + slot_duration: schedule.slot_duration, end_time: endTime, is_online: isOnline, - estimated_time: schedule.slot_duration, + estimated_time: schedule.slot_duration, } }); } @@ -314,13 +314,13 @@ export class AppointmentService { // penalty to be added later } - public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes?: number, newScheduledTime?: Date) : Promise { + public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes?: number, newScheduledTime?: Date, rescheduleDay?: boolean): Promise { const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); let updatedScheduledTime: Date; let updatedEndTime: Date; - if (minutes){ + if (minutes) { updatedScheduledTime = new Date(appointment.scheduled_time.getTime() + minutes * 60000); updatedEndTime = new Date(appointment.end_time.getTime() + minutes * 60000); } else { @@ -328,7 +328,9 @@ export class AppointmentService { updatedEndTime = new Date(newScheduledTime.getTime() + appointment.slot_duration * 60000); } - await this.validateDoctorAvailability(doctorId, appointment.clinic_id, updatedScheduledTime, updatedEndTime, appointmentId); + if (rescheduleDay !== true) { + await this.validateDoctorAvailability(doctorId, appointment.clinic_id, updatedScheduledTime, updatedEndTime, appointmentId); + } await prisma.appointment.update({ where: { @@ -382,23 +384,23 @@ export class AppointmentService { clinic_id: true, }, orderBy: { - scheduled_time: 'asc', + scheduled_time: 'asc', } - + }); const dayOfWeek = this.getDayOfWeek(newBaseDate.getDay()); const isOnline = await this.doctorIsOnline(doctorId); const clinicId = appointments[0]?.clinic_id || null; const schedule = await prisma.doctorSchedule.findFirst({ - where:{ + where: { day_of_week: dayOfWeek, doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, is_active: true, deleted_at: null, }, - select:{ + select: { slot_duration: true, buffer_time: true, } @@ -415,12 +417,14 @@ export class AppointmentService { // move to next slot currentSlotStart = new Date(currentSlotStart.getTime() + (schedule.slot_duration + schedule.buffer_time) * 60000); - } + } } - } + } } - public async rescheduleDayAppointments(doctorId: string, currentDate: Date, newDate: Date, keepOriginalSlots: boolean): Promise { + public async rescheduleDayAppointments(doctorId: string, currentDate: Date, minutes?: number, newDate?: Date, keepOriginalSlots?: boolean): Promise { + + const rescheduleDay: boolean = true; const startOfDay = new Date(currentDate); startOfDay.setHours(0, 0, 0, 0); @@ -443,11 +447,21 @@ export class AppointmentService { scheduled_time: true, }, orderBy: { - scheduled_time: 'asc', + scheduled_time: 'asc', } }); - await this.bulkRescheduleByDoctor(doctorId, appointments.map(app => app.id), null, newDate, keepOriginalSlots); + if (minutes) { + for (const appointment of appointments) { + await this.getAndValidateAppointment(appointment.id, doctorId); + await this.rescheduleAppointmentByDoctor(doctorId, appointment.id, minutes, undefined, rescheduleDay); + } + } + + else if (newDate) { + await this.bulkRescheduleByDoctor(doctorId, appointments.map(app => app.id), null, newDate, keepOriginalSlots); + } + } public async cancelAppointment(userId: string, appointmentId: string): Promise { @@ -486,16 +500,16 @@ export class AppointmentService { data: { cancelled_by: appointment.patient_id === userId ? 'PATIENT' : 'DOCTOR', deleted_at: new Date(), - modified_at: new Date(), + modified_at: new Date(), status: 'CANCELLED', } }); // penalty to be added later }; - public async getDoctorSchedule(doctorId: string) : Promise { + public async getDoctorSchedule(doctorId: string): Promise { const now = new Date(); - + const appointments = await prisma.appointment.findMany({ where: { doctor_id: doctorId, @@ -546,7 +560,7 @@ export class AppointmentService { clinic_address: app.clinic ? app.clinic.address : null, }; - if (!groupedByDate.has(dateKey)){ + if (!groupedByDate.has(dateKey)) { groupedByDate.set(dateKey, []); } groupedByDate.get(dateKey).push(doctorAppointment); @@ -565,19 +579,19 @@ export class AppointmentService { return schedule; } - private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[]{ + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[] { const slots: Omit[] = []; const start = new Date(startTime); const end = new Date(endTime); - + let currentTime = new Date(start); - while (currentTime < end){ - const slotEnd = new Date(currentTime.getTime() + slotDuration * 60000); - if (slotEnd <= end){ + while (currentTime < end) { + const slotEnd = new Date(currentTime.getTime() + slotDuration * 60000); + if (slotEnd <= end) { slots.push({ - start: this.formatTime(currentTime), - end: this.formatTime(slotEnd), + start: this.formatTime(currentTime), + end: this.formatTime(slotEnd), }); } // move to next slot (slot duration + buffer time) @@ -590,7 +604,7 @@ export class AppointmentService { // converts js representation of days (0-6) to prisma's enum private getDayOfWeek(jsDay: number): DayOfWeek { const days: DayOfWeek[] = [ - DayOfWeek.SUNDAY, + DayOfWeek.SUNDAY, DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, @@ -753,7 +767,7 @@ export class AppointmentService { // check for overlap const hasConflict = conflictingAppointments.some(existing => { - return this.doesSlotOverlap(newScheduledTime,newEndTime, new Date(existing.scheduled_time), new Date(existing.end_time)); + return this.doesSlotOverlap(newScheduledTime, newEndTime, new Date(existing.scheduled_time), new Date(existing.end_time)); }); if (hasConflict) { diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts new file mode 100644 index 0000000..615678d --- /dev/null +++ b/src/services/socket.service.ts @@ -0,0 +1,90 @@ +import {Server as HttpServer} from 'http'; +import {Server, Socket} from 'socket.io'; +import { Service } from 'typedi'; +import { verify } from 'jsonwebtoken'; +import { DataStoredInToken } from '@/interfaces'; +import { SECRET_KEY } from '@/config'; + +interface AuthenticatedSocket extends Socket { + userId?: string; + userRole?: string; +} + +@Service() +export class SocketService { + private io: Server; + // userId --> set of socketIds (each tab/device = different socketId) + private userSocketMap: Map> = new Map(); + + public initialize(httpServer: HttpServer): void { + this.io = new Server(httpServer, { + cors: { + origin: process.env.ORIGIN, + credentials: true, + // methods: ['GET', 'POST'], + }, + // polling is just a fallback if websocket fails + transports: ['websocket', 'polling'], + }); + } + + private async authMiddleware(socket: AuthenticatedSocket, next: (err?: Error) => void): Promise { + try { + const token = socket.handshake.auth.token || socket.handshake.headers['authorization']?.split(' ')[1]; + if (!token) { + return next(new Error('Authentication error: Token not provided')); + } + + const decoded = verify(token, SECRET_KEY) as DataStoredInToken; + socket.userId = decoded.id; + // socket.userRole = decoded.role; + next(); + } catch (error) { + next(new Error('Authentication error: Invalid token')); + } + } + + private handleConnection(socket: AuthenticatedSocket): void { + const userId = socket.userId; + + if (!userId) { + socket.disconnect(); + return; + } + + if (!this.userSocketMap.has(userId)) { + this.userSocketMap.set(userId, new Set()); + } + this.userSocketMap.get(userId)?.add(socket.id); + + // personal room (all tabs/devices get the event) + socket.join(`user_${userId}`); + + socket.on('disconnect', () => { + this.handleDisconnection(socket); + }); + + socket.emit('connected', { + message: 'Successfully connected to socket server', + userId: userId + }); + + // this.sendInitialAppointments(userId); + + + } + + private handleDisconnection(socket: AuthenticatedSocket): void { + const userId = socket.userId; + if (!userId) return; + + if (userId && this.userSocketMap.has(userId)) { + this.userSocketMap.get(userId).delete(socket.id); + + if (this.userSocketMap.get(userId).size === 0) { + this.userSocketMap.delete(userId); + } + } + } + +} diff --git a/src/swagger-output.json b/src/swagger-output.json index 3e84ea6..5210c6c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3911,19 +3911,19 @@ "tags": [ "Appointments" ], - "description": "Reschedule all confirmed appointments of a specific day to a new date", + "description": "Reschedule all appointments on a specific day by the authenticated doctor. \\ Rules: \\ (1) You must provide EITHER \"minutes\" OR \"newDate\" (not both). \\ (2) When using \"minutes\", all appointments on the specified day are shifted by the same number of minutes. \\ (3) When using \"newDate\": \\ - If \"keepOriginalSlots\" is true, appointments keep their original time-of-day but move to the new date. \\ - If \"keepOriginalSlots\" is false, appointments are reallocated sequentially based on the doctor schedule.", "parameters": [ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication (must be a doctor)", + "description": "Bearer token for authentication (doctor)", "required": true, "type": "string" }, { "name": "body", "in": "body", - "description": "Parameters for moving all appointments from one day to another", + "description": "Reschedule day parameters", "required": true, "schema": { "type": "object", @@ -3932,9 +3932,13 @@ "type": "string", "example": "2026-02-10T09:00:00.000Z" }, + "minutes": { + "type": "number", + "example": 15 + }, "newDate": { "type": "string", - "example": "2026-02-16T09:00:00.000Z" + "example": "2026-02-13T09:00:00.000Z" }, "keepOriginalSlots": { "type": "boolean", @@ -3946,7 +3950,7 @@ ], "responses": { "200": { - "description": "All appointments for the day were successfully rescheduled", + "description": "Appointments rescheduled successfully", "schema": { "type": "object", "properties": { @@ -3961,13 +3965,13 @@ } }, "400": { - "description": "Bad request (missing/invalid dates, conflicting parameters, etc.)", + "description": "Bad request - invalid or conflicting reschedule parameters", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Error message describing the issue (e.g. \"Current date and new date cannot be the same\", \"Invalid date format\", etc.)" + "example": "Error message describing the issue" } }, "xml": { @@ -3976,13 +3980,13 @@ } }, "401": { - "description": "Unauthorized - user not authenticated" + "description": "Unauthorized - doctor not authenticated" }, "403": { - "description": "Forbidden - authenticated user is not a doctor or not authorized" + "description": "Forbidden - one or more appointments do not belong to the authenticated doctor" }, "404": { - "description": "No confirmed appointments found for the specified current date" + "description": "No appointments found on the specified day" } } } From 06cc4b758c75119e1a56103f8145f9d0625d1689 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 31 Jan 2026 15:02:21 +0200 Subject: [PATCH 106/210] calculate queue position / get initial data for patient and doctor --- src/interfaces/auth.interface.ts | 5 ++ src/interfaces/queue.interface.ts | 6 ++ src/services/appointment.service.ts | 5 +- src/services/queue.service.ts | 95 +++++++++++++++++++++++++++++ src/services/socket.service.ts | 62 ++++++++++++++++--- 5 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 src/interfaces/queue.interface.ts diff --git a/src/interfaces/auth.interface.ts b/src/interfaces/auth.interface.ts index aa930a4..5dfe9e3 100644 --- a/src/interfaces/auth.interface.ts +++ b/src/interfaces/auth.interface.ts @@ -20,6 +20,11 @@ export interface TokenResponse { refreshToken?: RefreshTokenData; } +export interface SocketStoredInToken { + id: string; + role: string; +} + export interface RequestWithUser extends Request { user: User; } diff --git a/src/interfaces/queue.interface.ts b/src/interfaces/queue.interface.ts new file mode 100644 index 0000000..551e98c --- /dev/null +++ b/src/interfaces/queue.interface.ts @@ -0,0 +1,6 @@ +export interface QueuePosition { + position: number; + estimatedWaitMinutes: number; + patientsAhead: number; +} + diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index a0c6f03..5e4735f 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -228,6 +228,9 @@ export class AppointmentService { address: true, } } + }, + orderBy: { + scheduled_time: 'asc', } }); return appointments.map(appointment => ({ @@ -602,7 +605,7 @@ export class AppointmentService { } // converts js representation of days (0-6) to prisma's enum - private getDayOfWeek(jsDay: number): DayOfWeek { + public getDayOfWeek(jsDay: number): DayOfWeek { const days: DayOfWeek[] = [ DayOfWeek.SUNDAY, DayOfWeek.MONDAY, diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index e69de29..35e8b1a 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -0,0 +1,95 @@ +import prisma from '@/config/prisma'; +import { HttpException } from "@/exceptions/HttpException"; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { QueuePosition } from '@/interfaces/queue.interface'; +import { AppointmentService } from './appointment.service'; + +export class QueueService { + + private appointmentService = new AppointmentService(); + + public async calculateQueuePosition(appointmentId: string): Promise { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + }, + select: { + doctor_id: true, + clinic_id: true, + scheduled_time: true, + slot_duration: true, + } + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const dayOfWeek = this.appointmentService.getDayOfWeek(appointment.scheduled_time.getDay()); + + const schedule = await prisma.doctorSchedule.findFirst({ + where: { + doctor_id: appointment.doctor_id, + clinic_id: appointment.clinic_id, + day_of_week: dayOfWeek, + is_active: true, + deleted_at: null, + }, + select: { + buffer_time: true, + + } + }); + const bufferTime = schedule?.buffer_time || 0; + + const startOfDay = new Date(appointment.scheduled_time); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(appointment.scheduled_time); + endOfDay.setHours(23, 59, 59, 999); + + const todayAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: appointment.doctor_id, + scheduled_time: { + gte: startOfDay, + lte: endOfDay, + }, + deleted_at: null, + status: {in : ['CONFIRMED', 'COMPLETED']} + }, + orderBy: { + scheduled_time: 'asc', + }, + select: { + id: true, + scheduled_time: true, + slot_duration: true, + status: true, + } + }); + + const currentIdx = todayAppointments.findIndex(app => app.id === appointmentId); + + if (currentIdx === -1) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appointmentsAhead = todayAppointments.slice(0, currentIdx).filter(app => app.status !== 'COMPLETED'); + + const patientsAhead = appointmentsAhead.length; + const position = currentIdx + 1; + const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); + + return { + position, + estimatedWaitMinutes, + patientsAhead, + }; + + + + } +} diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 615678d..818a127 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -1,13 +1,17 @@ -import {Server as HttpServer} from 'http'; -import {Server, Socket} from 'socket.io'; +import { Server as HttpServer } from 'http'; +import { Server, Socket } from 'socket.io'; import { Service } from 'typedi'; import { verify } from 'jsonwebtoken'; -import { DataStoredInToken } from '@/interfaces'; +import { SocketStoredInToken } from '@/interfaces'; import { SECRET_KEY } from '@/config'; +import prisma from '@/config/prisma'; +import { AppointmentService } from './appointment.service'; +import { QueueService } from './queue.service'; +import { Container } from 'typedi'; interface AuthenticatedSocket extends Socket { - userId?: string; - userRole?: string; + userId?: string; + userRole?: string; } @Service() @@ -15,6 +19,8 @@ export class SocketService { private io: Server; // userId --> set of socketIds (each tab/device = different socketId) private userSocketMap: Map> = new Map(); + private appointmentService = Container.get(AppointmentService); + private queueService = Container.get(QueueService); public initialize(httpServer: HttpServer): void { this.io = new Server(httpServer, { @@ -35,9 +41,9 @@ export class SocketService { return next(new Error('Authentication error: Token not provided')); } - const decoded = verify(token, SECRET_KEY) as DataStoredInToken; + const decoded = verify(token, SECRET_KEY) as SocketStoredInToken; socket.userId = decoded.id; - // socket.userRole = decoded.role; + socket.userRole = decoded.role; next(); } catch (error) { next(new Error('Authentication error: Invalid token')); @@ -46,6 +52,7 @@ export class SocketService { private handleConnection(socket: AuthenticatedSocket): void { const userId = socket.userId; + const userRole = socket.userRole; if (!userId) { socket.disconnect(); @@ -64,13 +71,16 @@ export class SocketService { this.handleDisconnection(socket); }); - socket.emit('connected', { + socket.emit('connected', { message: 'Successfully connected to socket server', userId: userId }); - // this.sendInitialAppointments(userId); - + if (userRole === 'PATIENT') { + this.sendInitialPatientData(userId); + } else if (userRole === 'DOCTOR') { + // this.sendInitialDoctorData(userId); + } } @@ -87,4 +97,36 @@ export class SocketService { } } + private async sendInitialPatientData(patientId: string): Promise { + try { + const appointments = await this.appointmentService.getPatientAppointments(patientId); + const appointmentsWithQueue = await Promise.all(appointments.map(async (app) => { + const queuePosition = await this.queueService.calculateQueuePosition(app.id); + return { + ...app, + queuePosition, + }; + })); + this.io.to(`user_${patientId}`).emit('initial_data', { + appointments: appointmentsWithQueue, + }); + } + catch (error) { + console.error('error fetching initial patient data:', error); + } + } + + private async sendInitialDoctorData(doctorId: string): Promise { + try{ + const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + this.io.to(`user_${doctorId}`).emit('initial_data', { + schedule: schedule, + }); + } + catch (error) { + console.error('error fetching initial doctor data:', error); + } + } } + + From b40e2f1dd62b2ebc0b637bee09935d2520c9275d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 31 Jan 2026 17:03:29 +0200 Subject: [PATCH 107/210] get todays appointment with queue parameters --- src/controllers/appointment.controller.ts | 16 ++++ src/controllers/queue.controller.ts | 31 +++++++ src/interfaces/appointments.interface.ts | 16 ++++ .../migration.sql | 3 + src/prisma/schema.prisma | 2 + src/routes/appointment.route.ts | 46 ++++++++++ src/routes/queue.route.ts | 23 +++++ src/services/appointment.service.ts | 75 ++++++++++++++- src/services/queue.service.ts | 66 ++++++++++--- src/services/socket.service.ts | 11 +++ src/swagger-output.json | 92 +++++++++++++++++++ src/utils/errorMessages.ts | 4 + src/utils/responseMessages.ts | 8 ++ 13 files changed, 381 insertions(+), 12 deletions(-) create mode 100644 src/prisma/migrations/20260131133344_add_queue_parameters/migration.sql diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index faabfb3..249aa4c 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -101,6 +101,22 @@ export class AppointmentController { }); }); + public getTodayAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + + if (!patientId) { + const error = createBilingualError(400, ErrorMessages.PATIENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appointments = await this.appointmentService.getTodayAppointment(patientId); + const response = createMultiLangMessage(SuccessResponseMessages.PATIENT_TODAY_APPOINTMENT_RETRIEVED); + res.status(200).json({ + data: appointments, + ...response + }); + }); + public getPatientSelectedAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; const { appointmentId } = req.params; diff --git a/src/controllers/queue.controller.ts b/src/controllers/queue.controller.ts index e69de29..3b1de81 100644 --- a/src/controllers/queue.controller.ts +++ b/src/controllers/queue.controller.ts @@ -0,0 +1,31 @@ +import { Request, Response, NextFunction } from "express"; +import { RequestWithUser } from "@/interfaces"; +import { HttpException } from "@/exceptions/HttpException"; +import { catchAsync } from '@/utils/catchAsync'; +import Container from "typedi"; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; + +import { QueueService } from "@/services/queue.service"; + +export class QueueController { + + public queueService = Container.get(QueueService); + + public getQueuePosition = catchAsync(async (req: Request, res: Response): Promise => { + const { appointmentId } = req.params; + + if (!appointmentId) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const queuePosition = await this.queueService.getQueuePosition(appointmentId); + const response = createMultiLangMessage(SuccessResponseMessages.QUEUE_POSITION_RETRIEVED); + res.status(200).json({ + data: queuePosition, + ...response + }); + + }); +} \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index abbbdf4..4b2f29a 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -35,6 +35,22 @@ export interface PatientAppointment { clinic_address: string | null; } +export interface PatientTodayAppointment { + id: string; + status: AppointmentStatus; + is_online: boolean; + slot_duration: number; + doctor_name: string; + appointment_date: string; + start_time: string; + end_time: string; + clinic_name: string | null; + clinic_address: string | null; + position: number; + estimatedWaitMinutes: number; + patientsAhead: number; +} + export interface DoctorAppointment { id: string; status: AppointmentStatus; diff --git a/src/prisma/migrations/20260131133344_add_queue_parameters/migration.sql b/src/prisma/migrations/20260131133344_add_queue_parameters/migration.sql new file mode 100644 index 0000000..d22ef98 --- /dev/null +++ b/src/prisma/migrations/20260131133344_add_queue_parameters/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Appointments" ADD COLUMN "patients_ahead" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "position" INTEGER NOT NULL DEFAULT 0; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 6e3afc2..cfddd8d 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -78,6 +78,8 @@ model Appointment { scheduled_time DateTime is_online Boolean @default(false) is_completed Boolean @default(false) + position Int @default(0) + patients_ahead Int @default(0) estimated_time Float? created_at DateTime @default(now()) modified_at DateTime @updatedAt diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index f37a7ab..2c0ddc8 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -399,6 +399,52 @@ export class AppointmentRoute implements Routes { this.appointmentController.getPatientSelectedAppointment ); + this.router.get( + `${this.path}/patient/today-appointment`, + /* + #swagger.path = '/appointments/patient/today-appointment' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a patient)', + required: true, + type: 'string' + } + #swagger.description = 'Get todays appointment for the patient, including queue position and estimated wait time' + #swagger.responses[200] = { + description: 'Todays appointment details retrieved successfully', + schema: { + data: { + id: 'appointment-uuid', + status: 'CONFIRMED', + slot_duration: 30, + doctor_name: 'Dr. House', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park', + position: 5, + estimated_time: 60, + patients_ahead: 3 + } + } + } + #swagger.responses[400] = { + description: 'Bad request - patient ID missing' + } + #swagger.responses[401] = { + description: 'Unauthorized - patient not authenticated' + } + #swagger.responses[404] = { + description: 'No appointment found for today' + } + */ + AuthMiddleware, + this.appointmentController.getTodayAppointment + ); + this.router.patch( `${this.path}/patient/:appointmentId/reschedule`, /* diff --git a/src/routes/queue.route.ts b/src/routes/queue.route.ts index e69de29..20383c6 100644 --- a/src/routes/queue.route.ts +++ b/src/routes/queue.route.ts @@ -0,0 +1,23 @@ +import { Routes } from "@/interfaces"; +import { Router } from "express"; +import { QueueController } from "@/controllers/queue.controller"; +import { AuthMiddleware } from "@/middlewares/auth.middleware"; + + +export class QueueRoute implements Routes { + public path = '/queue'; + public router = Router(); + public queueController = new QueueController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.get( + `${this.path}/position/:appointmentId`, + AuthMiddleware, + this.queueController.getQueuePosition + ); + } +} \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 5e4735f..2564355 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,11 +5,14 @@ import { Service } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { DoctorAppointment, DoctorScheduleDay, PatientAppointment } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment } from '@/interfaces/appointments.interface'; +import { QueueService } from './queue.service'; @Service() export class AppointmentService { + private queueService = new QueueService(); + public async getAvailableDays(doctorId: string, clinicId: string | null): Promise { const daysAhead = 30 const availableDays: AvailableDay[] = []; @@ -291,6 +294,76 @@ export class AppointmentService { }; } + public async getTodayAppointment(patientId: string): Promise { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const endOfToday = new Date(); + endOfToday.setHours(23, 59, 59, 999); + + const appointment = await prisma.appointment.findFirst({ + where: { + patient_id: patientId, + scheduled_time: { + gte: today, + lte: endOfToday, + } + }, + select: { + id: true, + scheduled_time: true, + status: true, + is_online: true, + slot_duration: true, + end_time: true, + position: true, + estimated_time: true, + patients_ahead: true, + doctor: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + } + }); + + if (!appointment) { + return null; + } + + await this.queueService.calculateQueuePosition(appointment.id); + // other transactions could interfere so dont blame me + const refreshed = await prisma.appointment.findUnique({ + where: { id: appointment.id }, + select: { + position: true, + estimated_time: true, + patients_ahead: true, + } + }); + + return { + id: appointment.id, + status: appointment.status, + is_online: appointment.is_online, + slot_duration: appointment.slot_duration, + doctor_name: appointment.doctor.name, + appointment_date: this.formatDate(appointment.scheduled_time), + start_time: this.formatTime(appointment.scheduled_time), + end_time: this.formatTime(appointment.end_time), + clinic_name: appointment.clinic ? appointment.clinic.name : null, + clinic_address: appointment.clinic ? appointment.clinic.address : null, + position: refreshed.position, + estimatedWaitMinutes: refreshed.estimated_time, + patientsAhead: refreshed.patients_ahead, + }; + } + public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { const slotDuration = await prisma.appointment.findUnique({ where: { diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 35e8b1a..94bab45 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -2,13 +2,36 @@ import prisma from '@/config/prisma'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { QueuePosition } from '@/interfaces/queue.interface'; -import { AppointmentService } from './appointment.service'; +import { DayOfWeek } from '@prisma/client'; + export class QueueService { - private appointmentService = new AppointmentService(); + public async getQueuePosition(appointmentId: string): Promise { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + }, + select: { + position: true, + estimated_time: true, + patients_ahead: true, + } + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return { + position: appointment.position, + estimatedWaitMinutes: appointment.estimated_time, + patientsAhead: appointment.patients_ahead, + }; + } - public async calculateQueuePosition(appointmentId: string): Promise { + public async calculateQueuePosition(appointmentId: string): Promise { const appointment = await prisma.appointment.findUnique({ where: { id: appointmentId, @@ -26,7 +49,7 @@ export class QueueService { throw new HttpException(error.status, error.message, error.messageAr); } - const dayOfWeek = this.appointmentService.getDayOfWeek(appointment.scheduled_time.getDay()); + const dayOfWeek = this.getDayOfWeek(appointment.scheduled_time.getDay()); const schedule = await prisma.doctorSchedule.findFirst({ where: { @@ -81,15 +104,36 @@ export class QueueService { const patientsAhead = appointmentsAhead.length; const position = currentIdx + 1; - const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); - - return { - position, - estimatedWaitMinutes, - patientsAhead, - }; + // NOOTEEE --> now time - scheduled time but in mins + const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); + this.updateQueueParameters(appointmentId, position, patientsAhead, estimatedWaitMinutes); + } + private async updateQueueParameters(appointmentId: string, position: number, patientsAhead: number, estimatedWaitMinutes: number): Promise { + await prisma.appointment.update({ + where: { + id: appointmentId, + }, + data: { + position, + patients_ahead: patientsAhead, + estimated_time: estimatedWaitMinutes, + }, + }); } + + public getDayOfWeek(jsDay: number): DayOfWeek { + const days: DayOfWeek[] = [ + DayOfWeek.SUNDAY, + DayOfWeek.MONDAY, + DayOfWeek.TUESDAY, + DayOfWeek.WEDNESDAY, + DayOfWeek.THURSDAY, + DayOfWeek.FRIDAY, + DayOfWeek.SATURDAY, + ]; + return days[jsDay]; + } } diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 818a127..af3a9fb 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -33,6 +33,17 @@ export class SocketService { transports: ['websocket', 'polling'], }); } + public isUserConnected(userId: string): boolean { + return this.userSocketMap.has(userId) && this.userSocketMap.get(userId).size > 0; + } + + public getTotalConnectedUsers(): number { + return this.userSocketMap.size; + } + + public getIO(): Server { + return this.io; + } private async authMiddleware(socket: AuthenticatedSocket, next: (err?: Error) => void): Promise { try { diff --git a/src/swagger-output.json b/src/swagger-output.json index 5210c6c..5f0c24c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3580,6 +3580,98 @@ } } }, + "/appointments/patient/today-appointment": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get todays appointment for the patient, including queue position and estimated wait time", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a patient)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Todays appointment details retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "slot_duration": { + "type": "number", + "example": 30 + }, + "doctor_name": { + "type": "string", + "example": "Dr. House" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-03" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "09:30" + }, + "clinic_name": { + "type": "string", + "example": "New Cairo Medical Clinic" + }, + "clinic_address": { + "type": "string", + "example": "123 Main Street, Medical Park" + }, + "position": { + "type": "number", + "example": 5 + }, + "estimated_time": { + "type": "number", + "example": 60 + }, + "patients_ahead": { + "type": "number", + "example": 3 + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - patient ID missing" + }, + "401": { + "description": "Unauthorized - patient not authenticated" + }, + "404": { + "description": "No appointment found for today" + } + } + } + }, "/appointments/patient/{appointmentId}/reschedule": { "patch": { "tags": [ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index be99ee2..a97f06c 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -193,6 +193,10 @@ export const ErrorMessages = { en: "Appointment not found", ar: "الموعد غير موجود" }, + APPOINTMENT_ID_REQUIRED: { + en: "Appointment ID is required", + ar: "معرف الموعد مطلوب" + }, UNAUTHORIZED_APPOINTMENT_ACCESS: { en: "You are not authorized to access this appointment", ar: "غير مصرح لك بالوصول إلى هذا الموعد" diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index ee3e2c3..7244aee 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -166,6 +166,14 @@ export const SuccessResponseMessages = { message_en: "Appointment details retrieved successfully.", message_ar: "تم استرجاع تفاصيل الموعد بنجاح.", }, + PATIENT_TODAY_APPOINTMENT_RETRIEVED: { + message_en: "Patient's today appointment retrieved successfully.", + message_ar: "تم استرجاع موعد المريض لليوم بنجاح.", + }, + QUEUE_POSITION_RETRIEVED: { + message_en: "Queue position retrieved successfully.", + message_ar: "تم استرجاع موقعك في قائمة الانتظار بنجاح.", + }, } interface MultiLangMessageObj { From 070912498e86a90ee7ae2510766a1b762e723d68 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 31 Jan 2026 21:32:51 +0200 Subject: [PATCH 108/210] added files url retrieval for doctors upon getting all doctors by admin/super admin --- src/services/admin.service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 1a3663b..d7d3895 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -111,7 +111,13 @@ export class AdminService { select: { specialization: true, avg_time: true, - account_status: true + account_status: true, + fellowshipCertificateUrl: true, + graduationCertificateUrl: true, + mastersCertificateUrl: true, + membershipCardUrl: true, + unionSpecializationCertificateUrl: true, + professionalPracticeCardUrl: true, } }, } From a034046535cca893a22c051fe05875c5f3031120 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 31 Jan 2026 21:52:52 +0200 Subject: [PATCH 109/210] swagger updates --- src/routes/admin.route.ts | 12 +++- src/routes/superAdmin.route.ts | 12 +++- src/services/admin.service.ts | 8 ++- src/swagger-output.json | 116 ++++++++++++++++++++++++++++++--- 4 files changed, 133 insertions(+), 15 deletions(-) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 5ab7249..d9a2a1c 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -84,7 +84,11 @@ export class AdminRoute implements Routes { schema: { data:[ { id: '1' , email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED' }, photoUrl: null }], + photoUrl: null, + doctor: { + specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED', + mastersCertificateUrl: '', graduationCertificateUrl: '', fellowshipCertificateUrl: '', professionalPracticeCardUrl: '', membershipCardUrl: '', unionSpecializationCertificateUrl: '' + }}] , messageEn: 'Doctors retrieved successfully', messageAr: "تم استرجاع بيانات الأطباء بنجاح." } @@ -198,7 +202,11 @@ export class AdminRoute implements Routes { schema: { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status: 'APPROVED' }, photoUrl: null }, + photoUrl: null , + doctor: { + specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED', + mastersCertificateUrl: '', graduationCertificateUrl: '', fellowshipCertificateUrl: '', professionalPracticeCardUrl: '', membershipCardUrl: '', unionSpecializationCertificateUrl: '' + } }, messageEn: 'Doctor retrieved successfully', messageAr: "تم استرجاع بيانات الطبيب بنجاح." } diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 2f0f808..d39eeb3 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -216,7 +216,11 @@ export class SuperAdminRoute implements Routes { schema: { data: [{ id: '1', email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }], + photoUrl: null , + doctor: { + specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED', + mastersCertificateUrl: '', graduationCertificateUrl: '', fellowshipCertificateUrl: '', professionalPracticeCardUrl: '', membershipCardUrl: '', unionSpecializationCertificateUrl: '' + }}], messageEn: 'Doctors retrieved successfully', messageAr: "تم استرجاع الأطباء بنجاح" } @@ -255,7 +259,11 @@ export class SuperAdminRoute implements Routes { schema: { data: { email: 'doctor@example.com', name: 'Dr. Smith', role: 'DOCTOR', username: 'smith', phone : '1234567890', gender: 'MALE', isVerified: false, hasCompletedProfile: false, - doctor: { specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null, account_status:'Approved' }, photoUrl: null }, + photoUrl: null , + doctor: { + specialization: {key: 'CARDIOLOGY' , value: 'Cardiology'} , avg_time: null , account_status: 'APPROVED', + mastersCertificateUrl: '', graduationCertificateUrl: '', fellowshipCertificateUrl: '', professionalPracticeCardUrl: '', membershipCardUrl: '', unionSpecializationCertificateUrl: '' + } }, messageEn: 'Doctor retrieved successfully', messageAr: "تم استرجاع الطبيب بنجاح" } diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index d7d3895..c06cb05 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -147,7 +147,13 @@ export class AdminService { select: { specialization: true, avg_time: true, - account_status: true + account_status: true, + fellowshipCertificateUrl: true, + graduationCertificateUrl: true, + mastersCertificateUrl: true, + membershipCardUrl: true, + unionSpecializationCertificateUrl: true, + professionalPracticeCardUrl: true, } }, } diff --git a/src/swagger-output.json b/src/swagger-output.json index 3fa4415..03f46f3 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1377,6 +1377,7 @@ "type": "boolean", "example": false }, + "photoUrl": {}, "doctor": { "type": "object", "properties": { @@ -1397,10 +1398,33 @@ "account_status": { "type": "string", "example": "APPROVED" + }, + "mastersCertificateUrl": { + "type": "string", + "example": "" + }, + "graduationCertificateUrl": { + "type": "string", + "example": "" + }, + "fellowshipCertificateUrl": { + "type": "string", + "example": "" + }, + "professionalPracticeCardUrl": { + "type": "string", + "example": "" + }, + "membershipCardUrl": { + "type": "string", + "example": "" + }, + "unionSpecializationCertificateUrl": { + "type": "string", + "example": "" } } - }, - "photoUrl": {} + } } } }, @@ -1687,6 +1711,7 @@ "type": "boolean", "example": false }, + "photoUrl": {}, "doctor": { "type": "object", "properties": { @@ -1707,10 +1732,33 @@ "account_status": { "type": "string", "example": "APPROVED" + }, + "mastersCertificateUrl": { + "type": "string", + "example": "" + }, + "graduationCertificateUrl": { + "type": "string", + "example": "" + }, + "fellowshipCertificateUrl": { + "type": "string", + "example": "" + }, + "professionalPracticeCardUrl": { + "type": "string", + "example": "" + }, + "membershipCardUrl": { + "type": "string", + "example": "" + }, + "unionSpecializationCertificateUrl": { + "type": "string", + "example": "" } } - }, - "photoUrl": {} + } } }, "messageEn": { @@ -2232,6 +2280,7 @@ "type": "boolean", "example": false }, + "photoUrl": {}, "doctor": { "type": "object", "properties": { @@ -2251,11 +2300,34 @@ "avg_time": {}, "account_status": { "type": "string", - "example": "Approved" + "example": "APPROVED" + }, + "mastersCertificateUrl": { + "type": "string", + "example": "" + }, + "graduationCertificateUrl": { + "type": "string", + "example": "" + }, + "fellowshipCertificateUrl": { + "type": "string", + "example": "" + }, + "professionalPracticeCardUrl": { + "type": "string", + "example": "" + }, + "membershipCardUrl": { + "type": "string", + "example": "" + }, + "unionSpecializationCertificateUrl": { + "type": "string", + "example": "" } } - }, - "photoUrl": {} + } } } }, @@ -2346,6 +2418,7 @@ "type": "boolean", "example": false }, + "photoUrl": {}, "doctor": { "type": "object", "properties": { @@ -2365,11 +2438,34 @@ "avg_time": {}, "account_status": { "type": "string", - "example": "Approved" + "example": "APPROVED" + }, + "mastersCertificateUrl": { + "type": "string", + "example": "" + }, + "graduationCertificateUrl": { + "type": "string", + "example": "" + }, + "fellowshipCertificateUrl": { + "type": "string", + "example": "" + }, + "professionalPracticeCardUrl": { + "type": "string", + "example": "" + }, + "membershipCardUrl": { + "type": "string", + "example": "" + }, + "unionSpecializationCertificateUrl": { + "type": "string", + "example": "" } } - }, - "photoUrl": {} + } } }, "messageEn": { From 3bdaf6cb8a4ebd2954c865ae3814bf3508bd10fb Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 31 Jan 2026 22:20:29 +0200 Subject: [PATCH 110/210] fixed signup endpoint --- src/services/auth.service.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 98857c9..918a811 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -34,7 +34,7 @@ export class AuthService { const hashedPassword = await hash(userData.password, 10); const username = emailHandle; - const { password, ...userDataWithoutPassword } = userData; + const { rememberMe, password, ...userDataWithoutPassword } = userData; const createdUserData: User = await this.users.create({ data: { ...userDataWithoutPassword, username, password_hash: hashedPassword, @@ -199,7 +199,7 @@ export class AuthService { // Verify the refresh token const secretKey: string = REFRESH_TOKEN_SECRET; let decoded: DataStoredInToken; - + try { decoded = verify(refreshToken, secretKey) as DataStoredInToken; } catch (error) { @@ -226,11 +226,11 @@ export class AuthService { } // Get user - const user = await this.users.findUnique({ + const user = await this.users.findUnique({ where: { id: decoded.id }, include: { doctor: true } // Include doctor relation if needed }); - + if (!user) { const error = createBilingualError(401, ErrorMessages.USER_NOT_EXIST); throw new HttpException(error.status, error.message, error.messageAr); @@ -239,20 +239,20 @@ export class AuthService { // Revoke the old refresh token (token rotation for security) await this.refreshTokens.update({ where: { id: storedToken.id }, - data: { - is_revoked: true, - revoked_at: new Date() + data: { + is_revoked: true, + revoked_at: new Date() } }); // Create new access token const accessToken = this.createAccessToken(user); - + // Create new refresh token (token rotation) const newRefreshToken = await this.createRefreshToken(user); - + // Create cookies with both tokens - const cookies = this.createCookies({ + const cookies = this.createCookies({ accessToken, refreshToken: newRefreshToken }); From ac0fcbc488bf3fd9aa0a385709e28f23447f7ba9 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 31 Jan 2026 22:21:53 +0200 Subject: [PATCH 111/210] swagger updates in signup endpoint --- src/routes/auth.route.ts | 3 ++- src/swagger-output.json | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index e39bbfa..73254bb 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -31,7 +31,8 @@ export class AuthRoute implements Routes { $email: 'user@example.com', $name: 'John Doe', $phone: '1234567890', - $password: 'password123' + $password: 'password123', + $rememberMe: false } } #swagger.responses[201] = { diff --git a/src/swagger-output.json b/src/swagger-output.json index 03f46f3..5bf3977 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -71,13 +71,18 @@ "password": { "type": "string", "example": "password123" + }, + "rememberMe": { + "type": "boolean", + "example": false } }, "required": [ "email", "name", "phone", - "password" + "password", + "rememberMe" ] } } From c1cb0339499d38c96b36eae5cc4f3a7d19ea3089 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 1 Feb 2026 19:56:49 +0200 Subject: [PATCH 112/210] replace connections --- package-lock.json | 7408 +++++++++++++++------------ package.json | 2 +- src/app.ts | 16 +- src/services/appointment.service.ts | 2 +- src/services/queue.service.ts | 3 +- src/services/socket.service.ts | 80 +- 6 files changed, 4346 insertions(+), 3165 deletions(-) diff --git a/package-lock.json b/package-lock.json index a41ff00..d3b06a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", - "@types/socket.io": "^3.0.1", + "@types/socket.io": "^3.0.2", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", @@ -88,6 +88,8 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", @@ -98,6 +100,8 @@ }, "node_modules/@apidevtools/openapi-schemas": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "license": "MIT", "engines": { "node": ">=10" @@ -105,10 +109,14 @@ }, "node_modules/@apidevtools/swagger-methods": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", @@ -122,2034 +130,1600 @@ "openapi-types": ">=7" } }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "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.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "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, - "dependencies": { - "tslib": "^2.6.2" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.922.0.tgz", - "integrity": "sha512-cowHCdzir4KmT/MoRyp2RV3BAebjcpiyKU1pidu2D1lI87iGXlxNG7KXJ0W8mjQoGpKa2XcihDY/mtqd/6uVlA==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.922.0", - "@aws-sdk/credential-provider-node": "3.922.0", - "@aws-sdk/middleware-host-header": "3.922.0", - "@aws-sdk/middleware-logger": "3.922.0", - "@aws-sdk/middleware-recursion-detection": "3.922.0", - "@aws-sdk/middleware-user-agent": "3.922.0", - "@aws-sdk/region-config-resolver": "3.922.0", - "@aws-sdk/signature-v4-multi-region": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@aws-sdk/util-endpoints": "3.922.0", - "@aws-sdk/util-user-agent-browser": "3.922.0", - "@aws-sdk/util-user-agent-node": "3.922.0", - "@smithy/config-resolver": "^4.4.1", - "@smithy/core": "^3.17.2", - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/hash-node": "^4.2.4", - "@smithy/invalid-dependency": "^4.2.4", - "@smithy/middleware-content-length": "^4.2.4", - "@smithy/middleware-endpoint": "^4.3.6", - "@smithy/middleware-retry": "^4.4.6", - "@smithy/middleware-serde": "^4.2.4", - "@smithy/middleware-stack": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.5", - "@smithy/util-defaults-mode-node": "^4.2.7", - "@smithy/util-endpoints": "^3.2.4", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-retry": "^4.2.4", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.922.0.tgz", - "integrity": "sha512-jdHs7uy7cSpiMvrxhYmqHyJxgK7hyqw4plG8OQ4YTBpq0SbfAxdoOuOkwJ1IVUUQho4otR1xYYjiX/8e8J8qwQ==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.922.0", - "@aws-sdk/middleware-host-header": "3.922.0", - "@aws-sdk/middleware-logger": "3.922.0", - "@aws-sdk/middleware-recursion-detection": "3.922.0", - "@aws-sdk/middleware-user-agent": "3.922.0", - "@aws-sdk/region-config-resolver": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@aws-sdk/util-endpoints": "3.922.0", - "@aws-sdk/util-user-agent-browser": "3.922.0", - "@aws-sdk/util-user-agent-node": "3.922.0", - "@smithy/config-resolver": "^4.4.1", - "@smithy/core": "^3.17.2", - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/hash-node": "^4.2.4", - "@smithy/invalid-dependency": "^4.2.4", - "@smithy/middleware-content-length": "^4.2.4", - "@smithy/middleware-endpoint": "^4.3.6", - "@smithy/middleware-retry": "^4.4.6", - "@smithy/middleware-serde": "^4.2.4", - "@smithy/middleware-stack": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.5", - "@smithy/util-defaults-mode-node": "^4.2.7", - "@smithy/util-endpoints": "^3.2.4", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-retry": "^4.2.4", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.922.0.tgz", - "integrity": "sha512-EvfP4cqJfpO3L2v5vkIlTkMesPtRwWlMfsaW6Tpfm7iYfBOuTi6jx60pMDMTyJNVfh6cGmXwh/kj1jQdR+w99Q==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "dependencies": { - "@aws-sdk/types": "3.922.0", - "@aws-sdk/xml-builder": "3.921.0", - "@smithy/core": "^3.17.2", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/signature-v4": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.922.0.tgz", - "integrity": "sha512-WikGQpKkROJSK3D3E7odPjZ8tU7WJp5/TgGdRuZw3izsHUeH48xMv6IznafpRTmvHcjAbDQj4U3CJZNAzOK/OQ==", + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.922.0.tgz", - "integrity": "sha512-i72DgHMK7ydAEqdzU0Duqh60Q8W59EZmRJ73y0Y5oFmNOqnYsAI+UXyOoCsubp+Dkr6+yOwAn1gPt1XGE9Aowg==", + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/util-stream": "^4.5.5", - "tslib": "^2.6.2" + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.922.0.tgz", - "integrity": "sha512-bVF+pI5UCLNkvbiZr/t2fgTtv84s8FCdOGAPxQiQcw5qOZywNuuCCY3wIIchmQr6GJr8YFkEp5LgDCac5EC5aQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "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": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/credential-provider-env": "3.922.0", - "@aws-sdk/credential-provider-http": "3.922.0", - "@aws-sdk/credential-provider-process": "3.922.0", - "@aws-sdk/credential-provider-sso": "3.922.0", - "@aws-sdk/credential-provider-web-identity": "3.922.0", - "@aws-sdk/nested-clients": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/credential-provider-imds": "^4.2.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.922.0.tgz", - "integrity": "sha512-agCwaD6mBihToHkjycL8ObIS2XOnWypWZZWhJSoWyHwFrhEKz1zGvgylK9Dc711oUfU+zU6J8e0JPKNJMNb3BQ==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "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": { - "@aws-sdk/credential-provider-env": "3.922.0", - "@aws-sdk/credential-provider-http": "3.922.0", - "@aws-sdk/credential-provider-ini": "3.922.0", - "@aws-sdk/credential-provider-process": "3.922.0", - "@aws-sdk/credential-provider-sso": "3.922.0", - "@aws-sdk/credential-provider-web-identity": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/credential-provider-imds": "^4.2.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.922.0.tgz", - "integrity": "sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "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": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.922.0.tgz", - "integrity": "sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==", + "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": { - "@aws-sdk/client-sso": "3.922.0", - "@aws-sdk/core": "3.922.0", - "@aws-sdk/token-providers": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.922.0.tgz", - "integrity": "sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/nested-clients": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.922.0.tgz", - "integrity": "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==", + "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": { - "@aws-sdk/types": "3.922.0", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.922.0.tgz", - "integrity": "sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "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": { - "@aws-sdk/types": "3.922.0", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.922.0.tgz", - "integrity": "sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.922.0", - "@aws/lambda-invoke-store": "^0.1.1", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.922.0.tgz", - "integrity": "sha512-ygg8lME1oFAbsH42ed2wtGqfHLoT5irgx6VC4X98j79fV1qXEwwwbqMsAiMQ/HJehpjqAFRVsHox3MHLN48Z5A==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "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": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.17.2", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/signature-v4": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-stream": "^4.5.5", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.922.0.tgz", - "integrity": "sha512-N4Qx/9KP3oVQBJOrSghhz8iZFtUC2NNeSZt88hpPhbqAEAtuX8aD8OzVcpnAtrwWqy82Yd2YTxlkqMGkgqnBsQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "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": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@aws-sdk/util-endpoints": "3.922.0", - "@smithy/core": "^3.17.2", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.922.0.tgz", - "integrity": "sha512-uYvKCF1TGh/MuJ4TMqmUM0Csuao02HawcseG4LUDyxdUsd/EFuxalWq1Cx4fKZQ2K8F504efZBjctMAMNY+l7A==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.922.0", - "@aws-sdk/middleware-host-header": "3.922.0", - "@aws-sdk/middleware-logger": "3.922.0", - "@aws-sdk/middleware-recursion-detection": "3.922.0", - "@aws-sdk/middleware-user-agent": "3.922.0", - "@aws-sdk/region-config-resolver": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@aws-sdk/util-endpoints": "3.922.0", - "@aws-sdk/util-user-agent-browser": "3.922.0", - "@aws-sdk/util-user-agent-node": "3.922.0", - "@smithy/config-resolver": "^4.4.1", - "@smithy/core": "^3.17.2", - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/hash-node": "^4.2.4", - "@smithy/invalid-dependency": "^4.2.4", - "@smithy/middleware-content-length": "^4.2.4", - "@smithy/middleware-endpoint": "^4.3.6", - "@smithy/middleware-retry": "^4.4.6", - "@smithy/middleware-serde": "^4.2.4", - "@smithy/middleware-stack": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.5", - "@smithy/util-defaults-mode-node": "^4.2.7", - "@smithy/util-endpoints": "^3.2.4", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-retry": "^4.2.4", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "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" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.922.0.tgz", - "integrity": "sha512-44Y/rNNwhngR2KHp6gkx//TOr56/hx6s4l+XLjOqH7EBCHL7XhnrT1y92L+DLiroVr1tCSmO8eHQwBv0Y2+mvw==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "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": { - "@aws-sdk/types": "3.922.0", - "@smithy/config-resolver": "^4.4.1", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.922.0.tgz", - "integrity": "sha512-mmsgEEL5pE+A7gFYiJMDBCLVciaXq4EFI5iAP7bPpnHvOplnNOYxVy2IreKMllGvrfjVyLnwxzZYlo5zZ65FWg==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "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": { - "@aws-sdk/middleware-sdk-s3": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/protocol-http": "^5.3.4", - "@smithy/signature-v4": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.922.0.tgz", - "integrity": "sha512-/inmPnjZE0ZBE16zaCowAvouSx05FJ7p6BQYuzlJ8vxEU0sS0Hf8fvhuiRnN9V9eDUPIBY+/5EjbMWygXL4wlQ==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "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": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/nested-clients": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.922.0.tgz", - "integrity": "sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==", + "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": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", - "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "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": { - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.922.0.tgz", - "integrity": "sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.922.0", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-endpoints": "^3.2.4", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" + "node": ">=6.9.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.922.0.tgz", - "integrity": "sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==", + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.922.0", - "@smithy/types": "^4.8.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.922.0.tgz", - "integrity": "sha512-NrPe/Rsr5kcGunkog0eBV+bY0inkRELsD2SacC4lQZvZiXf8VJ2Y7j+Yq1tB+h+FPLsdt3v9wItIvDf/laAm0Q==", + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.921.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.921.0.tgz", - "integrity": "sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==", + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.8.1", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.1.tgz", - "integrity": "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==", + "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, + "license": "MIT" + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", + "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", "dev": true, - "engines": { - "node": ">=18.0.0" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "dev": true, + "node_modules/@chainsafe/is-ip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", + "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", + "license": "MIT" + }, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" + "@chainsafe/is-ip": "^2.0.1" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "dev": true, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=0.1.90" } }, - "node_modules/@babel/core": { - "version": "7.28.5", + "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, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=12" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", + "node_modules/@cspotcode/source-map-support/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, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "dev": true, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "dev": true, + "node_modules/@dnsquery/dns-packet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", + "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@leichtgewicht/ip-codec": "^2.0.4", + "utf8-codec": "^1.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=6" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, "engines": { - "node": ">=6.9.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=6.9.0" + "node": "*" } }, - "node_modules/@babel/helpers": { - "version": "7.28.4", + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@eslint/core": "^0.17.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=6.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 4" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "*" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "dev": true, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=12.10.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=18.18" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.10.1.tgz", + "integrity": "sha512-nIw4oUUhHtrgxH5UAu53Dy778+xf2eM7SwvBXQlJhx9vJQ7eYX4MF0BuGGDxaYsc8gNZddddj8n99ex3Z8+exw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-protos": "^0.3.0", + "@noble/curves": "^1.9.4", + "google-protobuf": "^3.21.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.9.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optionalDependencies": { + "pkcs11js": "^2.1.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "dev": true, - "license": "MIT", + "node_modules/@hyperledger/fabric-protos": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", + "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@grpc/grpc-js": "^1.11.0", + "google-protobuf": "^3.21.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.13.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "dev": true, - "license": "MIT", + "node_modules/@ipld/dag-cbor": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", + "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "cborg": "^4.0.0", + "multiformats": "^13.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "dev": true, - "license": "MIT", + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@ipld/dag-json": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.6.tgz", + "integrity": "sha512-51yc5azhmkvc9mp2HV/vtJ8SlgFXADp55wAPuuAjQZ+yPurAYuTVddS3ke5vT4sjcd4DbE+DWjsMZGXjFB2cuA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" + "cborg": "^4.4.0", + "multiformats": "^13.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "dev": true, - "license": "MIT", + "node_modules/@ipld/dag-json/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@ipld/dag-pb": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz", + "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "multiformats": "^13.1.0" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@borewit/text-codec": { - "version": "0.1.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT" - }, - "node_modules/@chainsafe/netmask": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", - "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", - "license": "MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { "node": ">=12" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.8", - "license": "MIT", - "dependencies": { - "@so-ric/colorspace": "^1.1.6", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@epic-web/invariant": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "minipass": "^7.0.4" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", + "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": "Apache-2.0", + "license": "ISC", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "sprintf-js": "~1.0.2" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/@istanbuljs/load-nyc-config/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": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", + "node_modules/@istanbuljs/load-nyc-config/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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "p-locate": "^4.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", + "node_modules/@istanbuljs/load-nyc-config/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": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "p-try": "^2.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "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": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=8" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/@istanbuljs/load-nyc-config/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } + "license": "BSD-3-Clause" }, - "node_modules/@eslint/js": { - "version": "9.39.0", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", - "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", - "license": "Apache-2.0", "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.18.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" }, "engines": { - "node": ">=18.18.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@hyperledger/fabric-gateway": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", - "integrity": "sha512-q5lFrzbKsKdMgMGhaEE4dVXtpQa4qyWMdD1RXJFki6BiiKOzZC7IEV3xj67ffSaD33iYztxomYxlHVQJqD21HQ==", - "license": "Apache-2.0", + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.14.0", - "@hyperledger/fabric-protos": "^0.3.0", - "@noble/curves": "^1.9.4", - "google-protobuf": "^3.21.0" + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "pkcs11js": "^2.1.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@hyperledger/fabric-protos": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", - "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.11.0", - "google-protobuf": "^3.21.0" - }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@ipld/dag-cbor": { - "version": "9.2.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", - "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@ipld/dag-cbor/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@ipld/dag-json": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.5.tgz", - "integrity": "sha512-Q4Fr3IBDEN8gkpgNefynJ4U/ZO5Kwr7WSUMBDbZx0c37t0+IwQCTM9yJh8l5L4SRFjm31MuHwniZ/kM+P7GQ3Q==", - "license": "Apache-2.0 OR MIT", + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" + "@types/node": "*", + "jest-regex-util": "30.0.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@ipld/dag-json/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@ipld/dag-pb": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz", - "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", "dependencies": { - "multiformats": "^13.1.0" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@ipld/dag-pb/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=12" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/console": { + "node_modules/@jest/test-sequencer": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/core": { + "node_modules/@jest/transform": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", + "@babel/core": "^7.27.4", "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "dev": true, - "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment": { + "node_modules/@jest/types": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "jest-mock": "30.2.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.2.0", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", + "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, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6.0.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/@jest/globals": { - "version": "30.2.0", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "dev": true, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/interface": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", + "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.1", + "main-event": "^1.0.1", + "multiformats": "^13.4.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^2.4.8" } }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, "node_modules/@libp2p/interface-connection": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", @@ -2183,9 +1757,9 @@ } }, "node_modules/@libp2p/interface-connection/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/interface-connection/node_modules/uint8arrays": { @@ -2254,9 +1828,9 @@ } }, "node_modules/@libp2p/interface-peer-info/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays": { @@ -2285,6 +1859,33 @@ "npm": ">=7.0.0" } }, + "node_modules/@libp2p/interface/node_modules/@multiformats/multiaddr": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", + "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/@libp2p/interfaces": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", @@ -2328,9 +1929,9 @@ } }, "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/logger/node_modules/uint8arrays": { @@ -2343,9 +1944,9 @@ } }, "node_modules/@libp2p/logger/node_modules/uint8arrays/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@libp2p/peer-id": { @@ -2365,47 +1966,23 @@ } }, "node_modules/@multiformats/dns": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", - "integrity": "sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.13.tgz", + "integrity": "sha512-yr4bxtA3MbvJ+2461kYIYMsiiZj/FIqKI64hE4SdvWJUdWF9EtZLar38juf20Sf5tguXKFUruluswAO6JsjS2w==", "license": "Apache-2.0 OR MIT", "dependencies": { - "buffer": "^6.0.3", - "dns-packet": "^5.6.1", + "@dnsquery/dns-packet": "^6.1.1", + "@libp2p/interface": "^3.1.0", "hashlru": "^2.3.0", "p-queue": "^9.0.0", "progress-events": "^1.0.0", "uint8arrays": "^5.0.2" } }, - "node_modules/@multiformats/dns/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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.2.1" - } - }, "node_modules/@multiformats/dns/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@multiformats/dns/node_modules/uint8arrays": { @@ -2460,9 +2037,9 @@ } }, "node_modules/@multiformats/multiaddr-to-uri/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/@multiformats/multiaddr-to-uri/node_modules/uint8arrays": { @@ -2474,14 +2051,10 @@ "multiformats": "^13.0.0" } }, - "node_modules/@multiformats/multiaddr/node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "license": "MIT" - }, "node_modules/@napi-rs/nice": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, "license": "MIT", "optional": true, @@ -2512,1121 +2085,907 @@ "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, - "node_modules/@napi-rs/nice-linux-x64-gnu": { + "node_modules/@napi-rs/nice-android-arm-eabi": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">= 10" } }, - "node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/agent": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, - "node_modules/@npmcli/fs": { - "version": "4.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=14" + "node": ">= 10" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@pm2/agent": { - "version": "2.1.1", - "dev": true, - "license": "AGPL-3.0", - "dependencies": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.1.0", - "fclone": "~1.0.11", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.4.0", - "semver": "~7.5.0", - "ws": "~7.5.10" + "node": ">= 10" } }, - "node_modules/@pm2/agent/node_modules/chalk": { - "version": "3.0.0", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/@pm2/agent/node_modules/dayjs": { - "version": "1.8.36", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/agent/node_modules/debug": { - "version": "4.3.7", + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@pm2/agent/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pm2/agent/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@pm2/agent/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/@pm2/blessed": { - "version": "0.1.81", + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "bin": { - "blessed": "bin/tput.js" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/@pm2/io": { - "version": "6.1.0", - "dev": true, - "license": "Apache-2", - "dependencies": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0" - } - }, - "node_modules/@pm2/io/node_modules/async": { - "version": "2.6.4", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" + "node": ">= 10" } }, - "node_modules/@pm2/io/node_modules/debug": { - "version": "4.3.7", + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@pm2/io/node_modules/eventemitter2": { - "version": "6.4.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/io/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pm2/io/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@pm2/io/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/@pm2/js-api": { - "version": "0.8.0", - "dev": true, - "license": "Apache-2", - "dependencies": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4.0" - } - }, - "node_modules/@pm2/js-api/node_modules/async": { - "version": "2.6.4", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" + "node": ">= 10" } }, - "node_modules/@pm2/js-api/node_modules/debug": { - "version": "4.3.7", + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 10" } }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.1" - } - }, - "node_modules/@prisma/client": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", - "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", - "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/config": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", - "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", - "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/debug": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", - "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==" - }, - "node_modules/@prisma/engines": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", - "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", - "hasInstallScript": true, - "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/fetch-engine": "6.18.0", - "@prisma/get-platform": "6.18.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", - "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==" - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", - "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", - "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/get-platform": "6.18.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", - "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", - "dependencies": { - "@prisma/debug": "6.18.0" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "node": ">= 10" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "hasInstallScript": true, - "license": "Apache-2.0" + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "node": ">= 10" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.4.tgz", - "integrity": "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==", - "dev": true, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "@noble/hashes": "1.8.0" }, "engines": { - "node": ">=18.0.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.1.tgz", - "integrity": "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ==", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.4", - "@smithy/util-middleware": "^4.2.4", - "tslib": "^2.6.2" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=18.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/core": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.2.tgz", - "integrity": "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "dependencies": { - "@smithy/middleware-serde": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-stream": "^4.5.5", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.4.tgz", - "integrity": "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "tslib": "^2.6.2" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.5.tgz", - "integrity": "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==", + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/querystring-builder": "^4.2.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": ">=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@smithy/hash-node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.4.tgz", - "integrity": "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==", + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/types": "^4.8.1", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "semver": "^7.3.5" }, "engines": { - "node": ">=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.4.tgz", - "integrity": "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@pm2/agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", + "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.1.0", + "fclone": "~1.0.11", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.4.0", + "semver": "~7.5.0", + "ws": "~7.5.10" } }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.4.tgz", - "integrity": "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==", + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.6.tgz", - "integrity": "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==", + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/core": "^3.17.2", - "@smithy/middleware-serde": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-middleware": "^4.2.4", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.6.tgz", - "integrity": "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==", + "node_modules/@pm2/agent/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==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/service-error-classification": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-retry": "^4.2.4", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" + "yallist": "^4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.4.tgz", - "integrity": "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==", + "node_modules/@pm2/agent/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==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.4.tgz", - "integrity": "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==", + "node_modules/@pm2/agent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "ISC" }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.4.tgz", - "integrity": "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==", + "node_modules/@pm2/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", "dev": true, - "dependencies": { - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "license": "MIT", + "bin": { + "blessed": "bin/tput.js" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.8.0" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.4.tgz", - "integrity": "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==", + "node_modules/@pm2/io": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", + "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", "dev": true, + "license": "Apache-2", "dependencies": { - "@smithy/abort-controller": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/querystring-builder": "^4.2.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" } }, - "node_modules/@smithy/property-provider": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.4.tgz", - "integrity": "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==", + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "lodash": "^4.17.14" } }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.4.tgz", - "integrity": "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==", + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.4.tgz", - "integrity": "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==", + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "dev": true, - "dependencies": { - "@smithy/types": "^4.8.1", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT" }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.4.tgz", - "integrity": "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==", + "node_modules/@pm2/io/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==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "yallist": "^4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.4.tgz", - "integrity": "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==", + "node_modules/@pm2/io/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==", "dev": true, + "license": "ISC", "dependencies": { - "@smithy/types": "^4.8.1" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.4.tgz", - "integrity": "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==", + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true, - "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "Apache-2.0" }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.4.tgz", - "integrity": "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==", + "node_modules/@pm2/io/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "ISC" }, - "node_modules/@smithy/smithy-client": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.2.tgz", - "integrity": "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==", + "node_modules/@pm2/js-api": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", + "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", "dev": true, + "license": "Apache-2", "dependencies": { - "@smithy/core": "^3.17.2", - "@smithy/middleware-endpoint": "^4.3.6", - "@smithy/middleware-stack": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-stream": "^4.5.5", - "tslib": "^2.6.2" + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^7.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=4.0" } }, - "node_modules/@smithy/types": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.1.tgz", - "integrity": "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==", + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "lodash": "^4.17.14" } }, - "node_modules/@smithy/url-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.4.tgz", - "integrity": "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==", + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "@smithy/querystring-parser": "^4.2.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "dev": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT" }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, + "debug": "^4.3.1" + } + }, + "node_modules/@prisma/client": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", + "hasInstallScript": true, + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "dev": true, + "node_modules/@prisma/config": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "dev": true, + "node_modules/@prisma/debug": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" } }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "dev": true, + "node_modules/@prisma/engines-version": { + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.5.tgz", - "integrity": "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==", - "dev": true, + "node_modules/@prisma/get-platform": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@prisma/debug": "6.18.0" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.7.tgz", - "integrity": "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw==", - "dev": true, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", "dependencies": { - "@smithy/config-resolver": "^4.4.1", - "@smithy/credential-provider-imds": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.4.tgz", - "integrity": "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==", - "dev": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.4.tgz", - "integrity": "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==", - "dev": true, - "dependencies": { - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" }, - "node_modules/@smithy/util-retry": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.4.tgz", - "integrity": "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==", - "dev": true, - "dependencies": { - "@smithy/service-error-classification": "^4.2.4", - "@smithy/types": "^4.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" }, - "node_modules/@smithy/util-stream": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.5.tgz", - "integrity": "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==", + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "dev": true, - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT" }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "node_modules/@sinonjs/commons": { + "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": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", "license": "MIT", "dependencies": { "color": "^5.0.2", @@ -3640,12 +2999,15 @@ "license": "MIT" }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" }, "node_modules/@swc/cli": { - "version": "0.7.8", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.10.tgz", + "integrity": "sha512-QQ36Q1VwGTT2YzvMeNe/j1x4DKS277DscNhWc57dIwQn//C+zAgvuSupMB/XkmYqPKQX+8hjn5/cHRJrMvWy0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3678,7 +3040,9 @@ } }, "node_modules/@swc/core": { - "version": "1.14.0", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", + "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -3694,16 +3058,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.14.0", - "@swc/core-darwin-x64": "1.14.0", - "@swc/core-linux-arm-gnueabihf": "1.14.0", - "@swc/core-linux-arm64-gnu": "1.14.0", - "@swc/core-linux-arm64-musl": "1.14.0", - "@swc/core-linux-x64-gnu": "1.14.0", - "@swc/core-linux-x64-musl": "1.14.0", - "@swc/core-win32-arm64-msvc": "1.14.0", - "@swc/core-win32-ia32-msvc": "1.14.0", - "@swc/core-win32-x64-msvc": "1.14.0" + "@swc/core-darwin-arm64": "1.15.11", + "@swc/core-darwin-x64": "1.15.11", + "@swc/core-linux-arm-gnueabihf": "1.15.11", + "@swc/core-linux-arm64-gnu": "1.15.11", + "@swc/core-linux-arm64-musl": "1.15.11", + "@swc/core-linux-x64-gnu": "1.15.11", + "@swc/core-linux-x64-musl": "1.15.11", + "@swc/core-win32-arm64-msvc": "1.15.11", + "@swc/core-win32-ia32-msvc": "1.15.11", + "@swc/core-win32-x64-msvc": "1.15.11" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3714,8 +3078,112 @@ } } }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", + "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", + "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", + "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", + "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", + "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.14.0", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", + "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", + "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", "cpu": [ "x64" ], @@ -3729,13 +3197,68 @@ "node": ">=10" } }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", + "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", + "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", + "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3744,6 +3267,8 @@ }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "license": "MIT", "dependencies": { @@ -3755,6 +3280,8 @@ }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", "dev": true, "license": "MIT", "dependencies": { @@ -3772,36 +3299,61 @@ }, "node_modules/@tokenizer/token": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "dev": true, "license": "MIT" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "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": { @@ -3814,6 +3366,8 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -3822,6 +3376,8 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", + "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": { @@ -3831,6 +3387,8 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3839,6 +3397,8 @@ }, "node_modules/@types/bcrypt": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3847,6 +3407,8 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -3856,6 +3418,8 @@ }, "node_modules/@types/compression": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3865,6 +3429,8 @@ }, "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, "license": "MIT", "dependencies": { @@ -3873,6 +3439,8 @@ }, "node_modules/@types/cookie-parser": { "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3881,11 +3449,15 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -3893,21 +3465,27 @@ }, "node_modules/@types/estree": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.5", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^1" + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3922,12 +3500,15 @@ "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/hpp": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.7.tgz", + "integrity": "sha512-YSQBkTwZepklRez0wgsljeewMytGNKgBAZR1YbmE0X49+elqkZ+fr/gvB407wL9Dl7a/Kv3W04yJueRmEHytBw==", "dev": true, "license": "MIT", "dependencies": { @@ -3935,22 +3516,30 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", "dev": true, "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "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, "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": { @@ -3959,6 +3548,8 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", + "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": { @@ -3967,6 +3558,8 @@ }, "node_modules/@types/jest": { "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { @@ -3976,10 +3569,14 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { @@ -3989,11 +3586,8 @@ }, "node_modules/@types/methods": { "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, "license": "MIT" }, @@ -4005,6 +3599,8 @@ }, "node_modules/@types/morgan": { "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", "dev": true, "license": "MIT", "dependencies": { @@ -4013,6 +3609,8 @@ }, "node_modules/@types/ms": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, @@ -4027,21 +3625,21 @@ } }, "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, "node_modules/@types/nodemailer": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.3.tgz", - "integrity": "sha512-fC8w49YQ868IuPWRXqPfLf+MuTRex5Z1qxMoG8rr70riqqbOp2F5xgOKE9fODEBPzpnvjkJXFgK6IL2xgMSTnA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, @@ -4050,6 +3648,7 @@ "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4059,6 +3658,7 @@ "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -4068,6 +3668,7 @@ "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.17.tgz", "integrity": "sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/passport": "*", @@ -4079,6 +3680,7 @@ "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/oauth": "*", @@ -4087,16 +3689,22 @@ }, "node_modules/@types/qs": { "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "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==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4104,28 +3712,21 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", + "@types/http-errors": "*", "@types/node": "*" } }, "node_modules/@types/socket.io": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.1.tgz", - "integrity": "sha512-XSma2FhVD78ymvoxYV4xGXrIH/0EKQ93rR+YR0Y+Kw1xbPzLDCip/UWSejZ08FpxYeYNci/PZPQS9anrvJRqMA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", + "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", + "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", "dev": true, "license": "MIT", "dependencies": { @@ -4134,11 +3735,15 @@ }, "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, "license": "MIT" }, "node_modules/@types/superagent": { "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4150,6 +3755,8 @@ }, "node_modules/@types/supertest": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "license": "MIT", "dependencies": { @@ -4159,11 +3766,15 @@ }, "node_modules/@types/swagger-jsdoc": { "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", "dev": true, "license": "MIT" }, "node_modules/@types/swagger-ui-express": { "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", "dev": true, "license": "MIT", "dependencies": { @@ -4173,14 +3784,20 @@ }, "node_modules/@types/triple-beam": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, "node_modules/@types/validator": { - "version": "13.15.4", + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.34", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { @@ -4189,23 +3806,26 @@ }, "node_modules/@types/yargs-parser": { "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, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4215,21 +3835,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.2", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4244,13 +3866,15 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4264,12 +3888,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4280,7 +3906,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "dev": true, "license": "MIT", "engines": { @@ -4295,15 +3923,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4318,7 +3948,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "dev": true, "license": "MIT", "engines": { @@ -4330,20 +3962,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4357,14 +3990,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4379,11 +4014,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.2", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4396,6 +4033,8 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4407,11 +4046,211 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -4422,8 +4261,69 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@xhmikosr/archive-type": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", + "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", "dev": true, "license": "MIT", "dependencies": { @@ -4435,6 +4335,8 @@ }, "node_modules/@xhmikosr/bin-check": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", + "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4447,6 +4349,8 @@ }, "node_modules/@xhmikosr/bin-wrapper": { "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", + "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4461,6 +4365,8 @@ }, "node_modules/@xhmikosr/decompress": { "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", + "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", "dev": true, "license": "MIT", "dependencies": { @@ -4477,6 +4383,8 @@ }, "node_modules/@xhmikosr/decompress-tar": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", + "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -4490,6 +4398,8 @@ }, "node_modules/@xhmikosr/decompress-tarbz2": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", + "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", "dev": true, "license": "MIT", "dependencies": { @@ -4505,6 +4415,8 @@ }, "node_modules/@xhmikosr/decompress-targz": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", + "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", "dev": true, "license": "MIT", "dependencies": { @@ -4518,6 +4430,8 @@ }, "node_modules/@xhmikosr/decompress-unzip": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", + "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", "dev": true, "license": "MIT", "dependencies": { @@ -4531,6 +4445,8 @@ }, "node_modules/@xhmikosr/downloader": { "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", + "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", "dev": true, "license": "MIT", "dependencies": { @@ -4550,6 +4466,8 @@ }, "node_modules/@xhmikosr/os-filter-obj": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", "dev": true, "license": "MIT", "dependencies": { @@ -4561,6 +4479,8 @@ }, "node_modules/abbrev": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { @@ -4575,6 +4495,8 @@ }, "node_modules/accepts": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -4586,6 +4508,8 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4593,6 +4517,8 @@ }, "node_modules/acorn": { "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -4604,6 +4530,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4612,6 +4540,8 @@ }, "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, "license": "MIT", "dependencies": { @@ -4623,6 +4553,8 @@ }, "node_modules/agent-base": { "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -4631,6 +4563,8 @@ }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -4646,11 +4580,15 @@ }, "node_modules/amp": { "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", "dev": true, "license": "MIT" }, "node_modules/amp-message": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", "dev": true, "license": "MIT", "dependencies": { @@ -4659,6 +4597,8 @@ }, "node_modules/ansi-colors": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", "engines": { @@ -4667,6 +4607,8 @@ }, "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": { @@ -4681,6 +4623,8 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -4692,6 +4636,8 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4705,6 +4651,8 @@ }, "node_modules/ansis": { "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "dev": true, "license": "ISC", "engines": { @@ -4719,6 +4667,8 @@ }, "node_modules/anymatch": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -4729,19 +4679,6 @@ "node": ">= 8" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4750,6 +4687,8 @@ }, "node_modules/arch": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", "dev": true, "funding": [ { @@ -4769,15 +4708,21 @@ }, "node_modules/arg": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { @@ -4786,11 +4731,15 @@ }, "node_modules/asap": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "node_modules/ast-types": { "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4802,15 +4751,21 @@ }, "node_modules/async": { "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "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, "license": "MIT" }, "node_modules/b4a": { "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4824,6 +4779,8 @@ }, "node_modules/babel-jest": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { @@ -4844,6 +4801,8 @@ }, "node_modules/babel-plugin-istanbul": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -4862,6 +4821,8 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { @@ -4873,6 +4834,8 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -4898,6 +4861,8 @@ }, "node_modules/babel-preset-jest": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4913,10 +4878,14 @@ }, "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==", "license": "MIT" }, "node_modules/bare-events": { - "version": "2.8.1", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4930,6 +4899,8 @@ }, "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", @@ -4959,12 +4930,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.22", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4973,6 +4947,8 @@ }, "node_modules/basic-auth": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" @@ -4983,10 +4959,14 @@ }, "node_modules/basic-auth/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==", "license": "MIT" }, "node_modules/basic-ftp": { - "version": "5.0.5", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", "dev": true, "license": "MIT", "engines": { @@ -4995,6 +4975,8 @@ }, "node_modules/bcrypt": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -5007,6 +4989,8 @@ }, "node_modules/bin-version": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", + "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", "dev": true, "license": "MIT", "dependencies": { @@ -5022,6 +5006,8 @@ }, "node_modules/bin-version-check": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", + "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -5060,13 +5046,15 @@ }, "node_modules/bodec": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", "dev": true, "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -5075,7 +5063,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -5087,30 +5075,10 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bowser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", - "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", - "dev": true - }, "node_modules/brace-expansion": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5119,6 +5087,8 @@ }, "node_modules/braces": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -5135,7 +5105,9 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/browserslist": { - "version": "4.27.0", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -5153,11 +5125,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -5168,6 +5140,8 @@ }, "node_modules/bs-logger": { "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", "dependencies": { @@ -5179,6 +5153,8 @@ }, "node_modules/bser": { "version": "2.1.1", + "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": { @@ -5186,8 +5162,9 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "dev": true, + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -5205,11 +5182,13 @@ "license": "MIT", "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/buffer-crc32": { "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { @@ -5218,10 +5197,14 @@ }, "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==", "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==", "license": "MIT" }, "node_modules/busboy": { @@ -5237,6 +5220,8 @@ }, "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" @@ -5246,6 +5231,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "license": "MIT", "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", @@ -5269,19 +5255,50 @@ } } }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 14.18.0" }, "funding": { - "url": "https://dotenvx.com" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/cacache": { "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { @@ -5304,11 +5321,15 @@ }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/cacheable-lookup": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, "license": "MIT", "engines": { @@ -5317,6 +5338,8 @@ }, "node_modules/cacheable-request": { "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5334,6 +5357,8 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5345,6 +5370,8 @@ }, "node_modules/call-bound": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5359,10 +5386,14 @@ }, "node_modules/call-me-maybe": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -5371,6 +5402,8 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -5378,7 +5411,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001752", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -5397,9 +5432,9 @@ "license": "CC-BY-4.0" }, "node_modules/cborg": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.3.0.tgz", - "integrity": "sha512-vOXo1pB4mdeBw3LbpoynQlZNw/H3kZVHLtPYlp8kFMreL/2YfT54F70BM1s3iDoCtQ+3C9QmiRF4rfCSSTlhBw==", + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.5.8.tgz", + "integrity": "sha512-6/viltD51JklRhq4L7jC3zgy6gryuG5xfZ3kzpE+PravtyeQLeQmCYLREhQH7pWENg5pY4Yu/XCd6a7dKScVlw==", "license": "Apache-2.0", "bin": { "cborg": "lib/bin.js" @@ -5407,6 +5442,8 @@ }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -5422,6 +5459,8 @@ }, "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": { @@ -5430,26 +5469,53 @@ }, "node_modules/charm": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", "dev": true, "license": "MIT/X11" }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 8.10.0" }, "funding": { "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" } }, "node_modules/chownr": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -5457,7 +5523,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -5474,30 +5542,39 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", "dependencies": { "consola": "^3.2.3" } }, "node_modules/cjs-module-lexer": { - "version": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "license": "MIT" }, "node_modules/class-validator": { - "version": "0.14.2", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", + "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", "license": "MIT", "dependencies": { - "@types/validator": "^13.11.8", + "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", - "validator": "^13.9.0" + "validator": "^13.15.20" } }, "node_modules/cli-cursor": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { @@ -5512,6 +5589,8 @@ }, "node_modules/cli-tableau": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", "dev": true, "dependencies": { "chalk": "3.0.0" @@ -5522,6 +5601,8 @@ }, "node_modules/cli-tableau/node_modules/chalk": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "license": "MIT", "dependencies": { @@ -5534,6 +5615,8 @@ }, "node_modules/cli-truncate": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { @@ -5548,7 +5631,9 @@ } }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.1.0", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dev": true, "license": "MIT", "dependencies": { @@ -5564,6 +5649,8 @@ }, "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==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5576,6 +5663,8 @@ }, "node_modules/cliui/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==", "license": "MIT", "engines": { "node": ">=8" @@ -5583,10 +5672,14 @@ }, "node_modules/cliui/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==", "license": "MIT" }, "node_modules/cliui/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==", "license": "MIT", "engines": { "node": ">=8" @@ -5594,6 +5687,8 @@ }, "node_modules/cliui/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==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5606,6 +5701,8 @@ }, "node_modules/cliui/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==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5616,6 +5713,8 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5630,13 +5729,12 @@ } }, "node_modules/cloudinary": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz", - "integrity": "sha512-s7frvR0HnQXeJsQSIsbLa/I09IMb1lOnVLEDH5b5E53WTiCYgrNNOBGV/i/nLHwrcEOUkqjfSwP1+enXWNYmdw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.9.0.tgz", + "integrity": "sha512-F3iKMOy4y0zy0bi5JBp94SC7HY7i/ImfTPSUV07iJmRzH1Iz8WavFfOlJTR1zvYM/xKGoiGZ3my/zy64In0IQQ==", "license": "MIT", "dependencies": { - "lodash": "^4.17.21", - "q": "^1.5.1" + "lodash": "^4.17.21" }, "engines": { "node": ">=9" @@ -5644,6 +5742,8 @@ }, "node_modules/co": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -5653,15 +5753,19 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, "node_modules/color": { - "version": "5.0.2", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", "license": "MIT", "dependencies": { - "color-convert": "^3.0.1", - "color-string": "^2.0.0" + "color-convert": "^3.1.3", + "color-string": "^2.1.3" }, "engines": { "node": ">=18" @@ -5669,6 +5773,8 @@ }, "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==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5679,10 +5785,14 @@ }, "node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/color-string": { - "version": "2.1.2", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5692,14 +5802,18 @@ } }, "node_modules/color-string/node_modules/color-name": { - "version": "2.0.2", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "engines": { "node": ">=12.20" } }, "node_modules/color/node_modules/color-convert": { - "version": "3.1.2", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5709,7 +5823,9 @@ } }, "node_modules/color/node_modules/color-name": { - "version": "2.0.2", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "engines": { "node": ">=12.20" @@ -5717,11 +5833,15 @@ }, "node_modules/colorette": { "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5733,6 +5853,8 @@ }, "node_modules/commander": { "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "license": "MIT", "engines": { @@ -5741,6 +5863,8 @@ }, "node_modules/component-emitter": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -5749,6 +5873,8 @@ }, "node_modules/compressible": { "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -5759,6 +5885,8 @@ }, "node_modules/compression": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -5775,6 +5903,8 @@ }, "node_modules/compression/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" @@ -5782,10 +5912,14 @@ }, "node_modules/compression/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/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, "node_modules/concat-stream": { @@ -5806,18 +5940,22 @@ "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==" + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5829,6 +5967,8 @@ }, "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" @@ -5836,11 +5976,15 @@ }, "node_modules/convert-source-map": { "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, "license": "MIT" }, "node_modules/cookie": { "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" @@ -5848,6 +5992,8 @@ }, "node_modules/cookie-parser": { "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { "cookie": "0.7.2", @@ -5859,15 +6005,21 @@ }, "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/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, "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -5875,20 +6027,30 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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, "license": "MIT" }, "node_modules/croner": { "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", "dev": true, "license": "MIT" }, "node_modules/cross-env": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, "license": "MIT", "dependencies": { @@ -5905,6 +6067,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -5918,6 +6082,8 @@ }, "node_modules/culvert": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", "dev": true, "license": "MIT" }, @@ -5933,6 +6099,8 @@ }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "license": "MIT", "engines": { @@ -5941,11 +6109,15 @@ }, "node_modules/dayjs": { "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5961,6 +6133,8 @@ }, "node_modules/decompress-response": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5975,6 +6149,8 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", "engines": { @@ -5985,7 +6161,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5999,11 +6177,15 @@ }, "node_modules/deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -6014,12 +6196,15 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" } }, "node_modules/defaults": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", + "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", "dev": true, "license": "MIT", "engines": { @@ -6031,6 +6216,8 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, "license": "MIT", "engines": { @@ -6040,10 +6227,13 @@ "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" }, "node_modules/degenerator": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6057,6 +6247,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==", "dev": true, "license": "MIT", "engines": { @@ -6065,6 +6257,8 @@ }, "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" @@ -6073,10 +6267,13 @@ "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==" + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" }, "node_modules/detect-newline": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -6085,6 +6282,8 @@ }, "node_modules/dezalgo": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "license": "ISC", "dependencies": { @@ -6104,6 +6303,8 @@ }, "node_modules/dir-glob": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -6125,20 +6326,10 @@ "undici": "^5.12.0" } }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/doctrine": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -6149,6 +6340,8 @@ }, "node_modules/dotenv": { "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -6162,6 +6355,7 @@ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", "dotenv": "^17.1.0", @@ -6177,6 +6371,7 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" }, @@ -6192,6 +6387,7 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -6201,6 +6397,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6213,11 +6411,15 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "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" @@ -6225,12 +6427,15 @@ }, "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/effect": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" @@ -6249,12 +6454,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.244", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "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": { @@ -6266,6 +6475,8 @@ }, "node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -6273,16 +6484,21 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/enabled": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "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" @@ -6290,11 +6506,25 @@ }, "node_modules/encoding": { "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" } }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/engine.io": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", @@ -6390,6 +6620,8 @@ }, "node_modules/enquirer": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "license": "MIT", "dependencies": { @@ -6401,6 +6633,8 @@ }, "node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -6408,7 +6642,9 @@ } }, "node_modules/envalid": { - "version": "8.1.0", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.1.tgz", + "integrity": "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -6419,6 +6655,8 @@ }, "node_modules/environment": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "engines": { @@ -6429,12 +6667,15 @@ } }, "node_modules/err-code": { - "version": "2.0.3", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6443,6 +6684,8 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6450,6 +6693,8 @@ }, "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" @@ -6457,6 +6702,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6467,6 +6714,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -6481,6 +6730,8 @@ }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -6488,10 +6739,14 @@ }, "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/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -6503,6 +6758,8 @@ }, "node_modules/escodegen": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6523,6 +6780,8 @@ }, "node_modules/escodegen/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", "optional": true, @@ -6531,7 +6790,9 @@ } }, "node_modules/eslint": { - "version": "9.39.0", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { @@ -6541,7 +6802,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.0", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6590,6 +6851,8 @@ }, "node_modules/eslint-config-prettier": { "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { @@ -6603,12 +6866,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -6633,6 +6898,8 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6648,6 +6915,8 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6659,6 +6928,8 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -6668,6 +6939,8 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6679,6 +6952,8 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -6687,6 +6962,8 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -6698,6 +6975,8 @@ }, "node_modules/espree": { "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6714,6 +6993,8 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6725,6 +7006,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "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": { @@ -6736,7 +7019,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6748,6 +7033,8 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6759,6 +7046,8 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6767,6 +7056,8 @@ }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -6774,6 +7065,8 @@ }, "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" @@ -6781,15 +7074,21 @@ }, "node_modules/eventemitter2": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", "dev": true, "license": "MIT" }, "node_modules/eventemitter3": { - "version": "5.0.1", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/events-universal": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6798,6 +7097,8 @@ }, "node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -6820,6 +7121,8 @@ }, "node_modules/exit-x": { "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, "license": "MIT", "engines": { @@ -6828,6 +7131,8 @@ }, "node_modules/expect": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -6844,20 +7149,25 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, "node_modules/express": { - "version": "5.1.0", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -6888,32 +7198,39 @@ } }, "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", + "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", + "safe-buffer": "~5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/express-session/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" } @@ -6921,20 +7238,26 @@ "node_modules/express-session/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express/node_modules/content-disposition": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie-signature": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -6943,10 +7266,13 @@ "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==" + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "license": "MIT" }, "node_modules/ext-list": { "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "license": "MIT", "dependencies": { @@ -6958,6 +7284,8 @@ }, "node_modules/ext-name": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6970,6 +7298,8 @@ }, "node_modules/extrareqp2": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", + "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", "dev": true, "license": "MIT", "dependencies": { @@ -6990,6 +7320,7 @@ "url": "https://opencollective.com/fast-check" } ], + "license": "MIT", "dependencies": { "pure-rand": "^6.1.0" }, @@ -7010,24 +7341,33 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, "node_modules/fast-fifo": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -7043,6 +7383,8 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -7054,44 +7396,36 @@ }, "node_modules/fast-json-patch": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "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, "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { - "version": "1.19.1", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -7100,6 +7434,8 @@ }, "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": { @@ -7108,36 +7444,28 @@ }, "node_modules/fclone": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", "dev": true, "license": "MIT" }, - "node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/fecha": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, "node_modules/fflate": { "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7149,6 +7477,8 @@ }, "node_modules/file-stream-rotator": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", "license": "MIT", "dependencies": { "moment": "^2.29.1" @@ -7156,6 +7486,8 @@ }, "node_modules/file-type": { "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", "dev": true, "license": "MIT", "dependencies": { @@ -7173,6 +7505,8 @@ }, "node_modules/filename-reserved-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, "license": "MIT", "engines": { @@ -7184,6 +7518,8 @@ }, "node_modules/filenamify": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7198,6 +7534,8 @@ }, "node_modules/fill-range": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -7208,7 +7546,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -7219,11 +7559,17 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -7239,6 +7585,8 @@ }, "node_modules/find-versions": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -7253,6 +7601,8 @@ }, "node_modules/flat-cache": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -7265,15 +7615,21 @@ }, "node_modules/flatted": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -7293,6 +7649,8 @@ }, "node_modules/foreground-child": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -7308,6 +7666,8 @@ }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -7318,7 +7678,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -7334,6 +7696,8 @@ }, "node_modules/form-data-encoder": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true, "license": "MIT", "engines": { @@ -7342,6 +7706,8 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "engines": { @@ -7350,6 +7716,8 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "dependencies": { @@ -7361,6 +7729,8 @@ }, "node_modules/formidable": { "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { @@ -7377,6 +7747,8 @@ }, "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" @@ -7384,6 +7756,8 @@ }, "node_modules/fresh": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7391,6 +7765,8 @@ }, "node_modules/fs-minipass": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, "license": "ISC", "dependencies": { @@ -7402,6 +7778,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==", "license": "ISC" }, "node_modules/fsevents": { @@ -7421,6 +7799,8 @@ }, "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" @@ -7428,6 +7808,8 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "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": { @@ -7436,6 +7818,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7443,6 +7827,8 @@ }, "node_modules/get-east-asian-width": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, "license": "MIT", "engines": { @@ -7454,6 +7840,8 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7482,6 +7870,8 @@ }, "node_modules/get-package-type": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -7490,6 +7880,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7501,6 +7893,8 @@ }, "node_modules/get-stream": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -7511,7 +7905,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", "dev": true, "license": "MIT", "dependencies": { @@ -7523,6 +7919,8 @@ }, "node_modules/get-uri": { "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -7538,6 +7936,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -7552,11 +7951,15 @@ }, "node_modules/git-node-fs": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", "dev": true, "license": "MIT" }, "node_modules/git-sha1": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", "dev": true, "license": "MIT" }, @@ -7583,6 +7986,8 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -7594,6 +7999,8 @@ }, "node_modules/globals": { "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -7605,6 +8012,8 @@ }, "node_modules/globby": { "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", "dependencies": { @@ -7624,6 +8033,8 @@ }, "node_modules/globby/node_modules/ignore": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -7638,6 +8049,8 @@ }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7648,6 +8061,8 @@ }, "node_modules/got": { "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, "license": "MIT", "dependencies": { @@ -7672,16 +8087,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "node_modules/handlebars": { "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7702,6 +8116,8 @@ }, "node_modules/handlebars/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": { @@ -7710,6 +8126,8 @@ }, "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, "license": "MIT", "engines": { @@ -7718,6 +8136,8 @@ }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7728,6 +8148,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -7748,6 +8170,8 @@ }, "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" @@ -7758,6 +8182,8 @@ }, "node_modules/helmet": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -7765,6 +8191,8 @@ }, "node_modules/hpp": { "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", "license": "ISC", "dependencies": { "lodash": "^4.17.12", @@ -7776,6 +8204,8 @@ }, "node_modules/hpp/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" @@ -7783,6 +8213,8 @@ }, "node_modules/hpp/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==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7790,6 +8222,8 @@ }, "node_modules/hpp/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==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -7800,6 +8234,8 @@ }, "node_modules/hpp/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", @@ -7811,37 +8247,42 @@ }, "node_modules/html-escaper": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-cache-semantics": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-errors": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -7854,6 +8295,8 @@ }, "node_modules/http2-wrapper": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7866,6 +8309,8 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -7878,6 +8323,8 @@ }, "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": { @@ -7886,6 +8333,8 @@ }, "node_modules/husky": { "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -7899,17 +8348,25 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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", @@ -7928,6 +8385,8 @@ }, "node_modules/ignore": { "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -7936,11 +8395,15 @@ }, "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, "license": "ISC" }, "node_modules/import-fresh": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7956,6 +8419,8 @@ }, "node_modules/import-local": { "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": { @@ -7974,6 +8439,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -7982,6 +8449,9 @@ }, "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.", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -7990,15 +8460,21 @@ }, "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/ini": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, "node_modules/inspect-with-kind": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", "dev": true, "license": "ISC", "dependencies": { @@ -8016,9 +8492,9 @@ } }, "node_modules/interface-datastore/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/interface-datastore/node_modules/uint8arrays": { @@ -8037,7 +8513,9 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/ip-address": { - "version": "10.0.1", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -8046,6 +8524,8 @@ }, "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" @@ -8147,12 +8627,6 @@ "npm": ">=7.0.0" } }, - "node_modules/ipfs-core-utils/node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "license": "MIT" - }, "node_modules/ipfs-http-client": { "version": "60.0.1", "resolved": "https://registry.npmjs.org/ipfs-http-client/-/ipfs-http-client-60.0.1.tgz", @@ -8185,12 +8659,6 @@ "npm": ">=7.0.0" } }, - "node_modules/ipfs-http-client/node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "license": "MIT" - }, "node_modules/ipfs-unixfs": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-9.0.1.tgz", @@ -8205,12 +8673,6 @@ "npm": ">=7.0.0" } }, - "node_modules/ipfs-unixfs/node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "license": "MIT" - }, "node_modules/ipfs-utils": { "version": "9.0.14", "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-9.0.14.tgz", @@ -8242,38 +8704,8 @@ "node_modules/ipfs-utils/node_modules/browser-readablestream-to-it": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz", - "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", - "license": "ISC" - }, - "node_modules/ipfs-utils/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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.2.1" - } - }, - "node_modules/ipfs-utils/node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "license": "MIT" + "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", + "license": "ISC" }, "node_modules/ipfs-utils/node_modules/it-all": { "version": "1.0.6", @@ -8310,6 +8742,8 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, @@ -8328,6 +8762,8 @@ }, "node_modules/is-core-module": { "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -8348,6 +8784,8 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -8356,6 +8794,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8370,6 +8810,8 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", + "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": { @@ -8378,6 +8820,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -8389,6 +8833,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -8396,19 +8842,24 @@ } }, "node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-promise": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -8419,6 +8870,8 @@ }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, @@ -8433,6 +8886,8 @@ }, "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": { @@ -8441,6 +8896,8 @@ }, "node_modules/istanbul-lib-instrument": { "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": { @@ -8456,6 +8913,8 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "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": { @@ -8469,6 +8928,8 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8482,6 +8943,8 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8607,30 +9070,6 @@ "readable-stream": "^3.6.0" } }, - "node_modules/it-to-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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.2.1" - } - }, "node_modules/it-to-stream/node_modules/p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -8642,6 +9081,8 @@ }, "node_modules/jackspeak": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -8656,6 +9097,8 @@ }, "node_modules/jest": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { @@ -8681,6 +9124,8 @@ }, "node_modules/jest-changed-files": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8694,6 +9139,8 @@ }, "node_modules/jest-circus": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { @@ -8724,6 +9171,8 @@ }, "node_modules/jest-cli": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { @@ -8755,6 +9204,8 @@ }, "node_modules/jest-config": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { @@ -8805,6 +9256,8 @@ }, "node_modules/jest-diff": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { @@ -8819,6 +9272,8 @@ }, "node_modules/jest-docblock": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -8830,6 +9285,8 @@ }, "node_modules/jest-each": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8845,6 +9302,8 @@ }, "node_modules/jest-environment-node": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { @@ -8862,6 +9321,8 @@ }, "node_modules/jest-haste-map": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -8885,6 +9346,8 @@ }, "node_modules/jest-leak-detector": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8897,6 +9360,8 @@ }, "node_modules/jest-matcher-utils": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -8911,6 +9376,8 @@ }, "node_modules/jest-message-util": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { @@ -8930,6 +9397,8 @@ }, "node_modules/jest-mock": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { @@ -8943,6 +9412,8 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { @@ -8959,6 +9430,8 @@ }, "node_modules/jest-regex-util": { "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { @@ -8967,6 +9440,8 @@ }, "node_modules/jest-resolve": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { @@ -8985,6 +9460,8 @@ }, "node_modules/jest-resolve-dependencies": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { @@ -8997,6 +9474,8 @@ }, "node_modules/jest-runner": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9029,6 +9508,8 @@ }, "node_modules/jest-runtime": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { @@ -9061,6 +9542,8 @@ }, "node_modules/jest-snapshot": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { @@ -9092,6 +9575,8 @@ }, "node_modules/jest-util": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { @@ -9106,8 +9591,23 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-validate": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -9124,6 +9624,8 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -9135,6 +9637,8 @@ }, "node_modules/jest-watcher": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { @@ -9153,6 +9657,8 @@ }, "node_modules/jest-worker": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { @@ -9168,6 +9674,8 @@ }, "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": { @@ -9182,6 +9690,8 @@ }, "node_modules/jiti": { "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -9189,6 +9699,8 @@ }, "node_modules/js-git": { "version": "0.7.8", + "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", + "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", "dev": true, "license": "MIT", "dependencies": { @@ -9200,6 +9712,8 @@ }, "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, "license": "MIT" }, @@ -9217,6 +9731,8 @@ }, "node_modules/jsesc": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -9228,32 +9744,44 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC", "optional": true }, "node_modules/json5": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -9264,10 +9792,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -9284,7 +9814,9 @@ } }, "node_modules/jwa": { - "version": "1.4.2", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -9293,17 +9825,19 @@ } }, "node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^1.4.2", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -9312,6 +9846,8 @@ }, "node_modules/kind-of": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { @@ -9320,10 +9856,14 @@ }, "node_modules/kuler": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", + "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": { @@ -9332,6 +9872,8 @@ }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9343,20 +9885,26 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.25", + "version": "1.12.36", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.36.tgz", + "integrity": "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ==", "license": "MIT" }, "node_modules/lines-and-columns": { "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, "license": "MIT" }, "node_modules/lint-staged": { - "version": "16.2.6", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.1", + "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", @@ -9375,7 +9923,9 @@ } }, "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.2", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { @@ -9384,6 +9934,8 @@ }, "node_modules/listr2": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { @@ -9400,6 +9952,8 @@ }, "node_modules/listr2/node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -9411,11 +9965,15 @@ }, "node_modules/listr2/node_modules/emoji-regex": { "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9432,6 +9990,8 @@ }, "node_modules/listr2/node_modules/wrap-ansi": { "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -9448,6 +10008,8 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -9474,56 +10036,84 @@ }, "node_modules/lodash.get": { "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, "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==", "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==", "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "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==", "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==", "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==", "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==", "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "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==", "license": "MIT" }, "node_modules/log-update": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "license": "MIT", "dependencies": { @@ -9541,7 +10131,9 @@ } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.1.1", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, "license": "MIT", "dependencies": { @@ -9556,6 +10148,8 @@ }, "node_modules/log-update/node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -9567,11 +10161,15 @@ }, "node_modules/log-update/node_modules/emoji-regex": { "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9588,6 +10186,8 @@ }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -9604,6 +10204,8 @@ }, "node_modules/logform": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -9625,6 +10227,8 @@ }, "node_modules/lowercase-keys": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "license": "MIT", "engines": { @@ -9636,14 +10240,24 @@ }, "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, "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, + "node_modules/main-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.1.tgz", + "integrity": "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==", + "license": "Apache-2.0 OR MIT" + }, "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, "license": "MIT", "dependencies": { @@ -9658,11 +10272,15 @@ }, "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, "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9684,6 +10302,8 @@ }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -9692,6 +10312,8 @@ }, "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": { @@ -9700,6 +10322,8 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9707,6 +10331,8 @@ }, "node_modules/media-typer": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9714,6 +10340,8 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -9734,22 +10362,17 @@ "node": ">=10" } }, - "node_modules/merge-options/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/merge-stream": { "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, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -9758,6 +10381,8 @@ }, "node_modules/methods": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", "engines": { @@ -9766,6 +10391,8 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -9776,21 +10403,10 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "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": { @@ -9802,23 +10418,33 @@ }, "node_modules/mime-db": { "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.1", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -9827,6 +10453,8 @@ }, "node_modules/mimic-function": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { @@ -9838,6 +10466,8 @@ }, "node_modules/mimic-response": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "license": "MIT", "engines": { @@ -9849,6 +10479,8 @@ }, "node_modules/minimatch": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -9863,6 +10495,8 @@ }, "node_modules/minimist": { "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" @@ -9870,6 +10504,8 @@ }, "node_modules/minipass": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -9878,6 +10514,8 @@ }, "node_modules/minipass-collect": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "license": "ISC", "dependencies": { @@ -9889,6 +10527,8 @@ }, "node_modules/minipass-fetch": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9905,6 +10545,8 @@ }, "node_modules/minipass-flush": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "license": "ISC", "dependencies": { @@ -9916,6 +10558,8 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9927,11 +10571,15 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { @@ -9943,6 +10591,8 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9954,11 +10604,15 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { @@ -9970,6 +10624,8 @@ }, "node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -9981,11 +10637,15 @@ }, "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minizlib": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -9996,23 +10656,28 @@ } }, "node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, + "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/module-details-from-path": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "dev": true, "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { "node": "*" @@ -10020,6 +10685,8 @@ }, "node_modules/morgan": { "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", @@ -10034,6 +10701,8 @@ }, "node_modules/morgan/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" @@ -10041,10 +10710,14 @@ }, "node_modules/morgan/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/morgan/node_modules/on-finished": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -10055,6 +10728,8 @@ }, "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==", "license": "MIT" }, "node_modules/multer": { @@ -10105,18 +10780,6 @@ "node": ">= 0.6" } }, - "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==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/multer/node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -10142,15 +10805,19 @@ }, "node_modules/mute-stream": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true, "license": "ISC" }, "node_modules/mylas": { - "version": "2.1.13", + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" }, "funding": { "type": "github", @@ -10159,6 +10826,8 @@ }, "node_modules/nano-spawn": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, "license": "MIT", "engines": { @@ -10188,6 +10857,8 @@ }, "node_modules/napi-postinstall": { "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -10211,11 +10882,15 @@ }, "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, "license": "MIT" }, "node_modules/needle": { "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "dev": true, "license": "MIT", "dependencies": { @@ -10232,6 +10907,8 @@ }, "node_modules/needle/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10240,6 +10917,8 @@ }, "node_modules/needle/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==", "dev": true, "license": "MIT", "dependencies": { @@ -10251,6 +10930,8 @@ }, "node_modules/negotiator": { "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10258,11 +10939,15 @@ }, "node_modules/neo-async": { "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/netmask": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, "license": "MIT", "engines": { @@ -10271,6 +10956,8 @@ }, "node_modules/node-addon-api": { "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -10278,6 +10965,8 @@ }, "node_modules/node-config": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/node-config/-/node-config-0.0.2.tgz", + "integrity": "sha512-NZu10oQ7jN6eDkRK22YX8j87mS02CuarKqoWIPcU6MKbuQ5dfLkvjOsWyN4ov+hPkIR7BppEueUg3QtcsRO7MA==", "dev": true, "engines": { "node": ">=0.1.99" @@ -10306,10 +10995,13 @@ "node_modules/node-fetch-native": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==" + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" }, "node_modules/node-gyp": { "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10333,6 +11025,8 @@ }, "node_modules/node-gyp-build": { "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -10342,6 +11036,8 @@ }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "license": "ISC", "engines": { @@ -10350,6 +11046,8 @@ }, "node_modules/node-gyp/node_modules/which": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -10364,24 +11062,31 @@ }, "node_modules/node-int64": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "3.1.10", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", "dev": true, "license": "MIT", "dependencies": { @@ -10409,6 +11114,8 @@ }, "node_modules/nodemon/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -10416,46 +11123,10 @@ "concat-map": "0.0.1" } }, - "node_modules/nodemon/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/nodemon/node_modules/glob-parent": { - "version": "5.1.2", - "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" - }, - "engines": { - "node": ">= 6" - } - }, "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, "license": "MIT", "engines": { @@ -10464,6 +11135,8 @@ }, "node_modules/nodemon/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -10473,34 +11146,10 @@ "node": "*" } }, - "node_modules/nodemon/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "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": { @@ -10512,6 +11161,8 @@ }, "node_modules/nopt": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { @@ -10526,6 +11177,8 @@ }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { @@ -10533,7 +11186,9 @@ } }, "node_modules/normalize-url": { - "version": "8.1.0", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", "dev": true, "license": "MIT", "engines": { @@ -10545,6 +11200,8 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -10555,30 +11212,38 @@ } }, "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", + "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", + "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", + "citty": "^0.2.0", "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" + "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", + "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", + "license": "MIT" + }, "node_modules/oauth": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", - "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==" + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" }, "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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10586,6 +11251,8 @@ }, "node_modules/object-hash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -10593,6 +11260,8 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10604,10 +11273,13 @@ "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==" + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" }, "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" @@ -10618,6 +11290,8 @@ }, "node_modules/on-headers": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10625,6 +11299,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==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -10632,6 +11308,8 @@ }, "node_modules/one-time": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { "fn.name": "1.x.x" @@ -10639,6 +11317,8 @@ }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -10660,6 +11340,8 @@ }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -10676,6 +11358,8 @@ }, "node_modules/p-cancelable": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "license": "MIT", "engines": { @@ -10715,6 +11399,8 @@ }, "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, "license": "MIT", "dependencies": { @@ -10729,6 +11415,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -10742,7 +11430,9 @@ } }, "node_modules/p-map": { - "version": "7.0.3", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -10753,9 +11443,9 @@ } }, "node_modules/p-queue": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", - "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", @@ -10782,6 +11472,8 @@ }, "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": { @@ -10790,6 +11482,8 @@ }, "node_modules/pac-proxy-agent": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, "license": "MIT", "dependencies": { @@ -10808,6 +11502,8 @@ }, "node_modules/pac-resolver": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "license": "MIT", "dependencies": { @@ -10820,16 +11516,22 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "dev": true, "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -10847,6 +11549,8 @@ }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -10864,6 +11568,8 @@ }, "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" @@ -10873,6 +11579,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -10890,6 +11597,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", "dependencies": { "passport-oauth2": "1.x.x" }, @@ -10901,6 +11609,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", "dependencies": { "base64url": "3.x.x", "oauth": "0.10.x", @@ -10926,6 +11635,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -10934,6 +11645,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10941,6 +11654,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -10949,11 +11664,15 @@ }, "node_modules/path-parse": { "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, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10969,11 +11688,15 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -10982,6 +11705,8 @@ }, "node_modules/path-type": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -10991,7 +11716,8 @@ "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" }, "node_modules/pause": { "version": "0.0.1", @@ -11000,27 +11726,32 @@ }, "node_modules/pend": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" }, "node_modules/picocolors": { "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": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -11028,6 +11759,8 @@ }, "node_modules/pidtree": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, "license": "MIT", "bin": { @@ -11039,6 +11772,8 @@ }, "node_modules/pidusage": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", + "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", "dev": true, "license": "MIT", "dependencies": { @@ -11050,6 +11785,8 @@ }, "node_modules/pirates": { "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -11058,6 +11795,8 @@ }, "node_modules/piscina": { "version": "4.9.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", + "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -11081,6 +11820,8 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", + "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": { @@ -11092,6 +11833,8 @@ }, "node_modules/pkg-dir/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": { @@ -11104,6 +11847,8 @@ }, "node_modules/pkg-dir/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": { @@ -11115,6 +11860,8 @@ }, "node_modules/pkg-dir/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": { @@ -11129,6 +11876,8 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", + "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": { @@ -11142,6 +11891,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", @@ -11150,6 +11900,8 @@ }, "node_modules/plimit-lit": { "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", "dev": true, "license": "MIT", "dependencies": { @@ -11211,6 +11963,8 @@ }, "node_modules/pm2-axon": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", + "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", "dev": true, "license": "MIT", "dependencies": { @@ -11225,6 +11979,8 @@ }, "node_modules/pm2-axon-rpc": { "version": "0.7.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", + "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", "dev": true, "license": "MIT", "dependencies": { @@ -11236,6 +11992,8 @@ }, "node_modules/pm2-deploy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", "dev": true, "license": "MIT", "dependencies": { @@ -11248,6 +12006,8 @@ }, "node_modules/pm2-multimeter": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", "dev": true, "license": "MIT/X11", "dependencies": { @@ -11256,6 +12016,8 @@ }, "node_modules/pm2-sysmonit": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", "dev": true, "license": "Apache", "optional": true, @@ -11269,87 +12031,42 @@ }, "node_modules/pm2-sysmonit/node_modules/pidusage": { "version": "2.0.21", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", + "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pm2/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/pm2/node_modules/commander": { - "version": "2.15.1", - "dev": true, - "license": "MIT" - }, - "node_modules/pm2/node_modules/glob-parent": { - "version": "5.1.2", - "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" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pm2/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" + "optional": true, + "dependencies": { + "safe-buffer": "^5.2.1" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=8" } }, - "node_modules/pm2/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/pm2/node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/pm2/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" } }, "node_modules/pm2/node_modules/semver": { "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -11361,6 +12078,8 @@ }, "node_modules/pm2/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": { @@ -11369,6 +12088,8 @@ }, "node_modules/pm2/node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -11378,6 +12099,8 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -11385,7 +12108,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -11399,7 +12124,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -11411,6 +12138,8 @@ }, "node_modules/pretty-format": { "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { @@ -11424,6 +12153,8 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -11438,6 +12169,7 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" @@ -11459,6 +12191,8 @@ }, "node_modules/proc-log": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { @@ -11473,6 +12207,8 @@ }, "node_modules/promise-retry": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "license": "MIT", "dependencies": { @@ -11483,8 +12219,17 @@ "node": ">=10" } }, + "node_modules/promise-retry/node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/promptly": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", "dev": true, "license": "MIT", "dependencies": { @@ -11517,6 +12262,8 @@ }, "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", @@ -11528,6 +12275,8 @@ }, "node_modules/proxy-agent": { "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11546,6 +12295,8 @@ }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -11554,16 +12305,22 @@ }, "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==", "dev": true, "license": "MIT" }, "node_modules/pstree.remy": { "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, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -11572,6 +12329,8 @@ }, "node_modules/pure-rand": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -11585,17 +12344,6 @@ ], "license": "MIT" }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -11613,6 +12361,8 @@ }, "node_modules/queue-lit": { "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", "dev": true, "license": "MIT", "engines": { @@ -11621,6 +12371,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -11640,6 +12392,8 @@ }, "node_modules/quick-lru": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "license": "MIT", "engines": { @@ -11653,48 +12407,40 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "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": "3.0.1", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -11702,6 +12448,8 @@ }, "node_modules/react-is": { "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" }, @@ -11725,6 +12473,8 @@ }, "node_modules/read": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "license": "ISC", "dependencies": { @@ -11736,6 +12486,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==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11747,16 +12499,16 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 14.18.0" + "dependencies": { + "picomatch": "^2.2.1" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=8.10.0" } }, "node_modules/receptacle": { @@ -11770,10 +12522,14 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11781,6 +12537,8 @@ }, "node_modules/require-in-the-middle": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", "dev": true, "license": "MIT", "dependencies": { @@ -11794,6 +12552,8 @@ }, "node_modules/resolve": { "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11813,11 +12573,15 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true, "license": "MIT" }, "node_modules/resolve-cwd": { "version": "3.0.0", + "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": { @@ -11829,6 +12593,8 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -11837,6 +12603,8 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -11845,6 +12613,8 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -11853,6 +12623,8 @@ }, "node_modules/responselike": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "license": "MIT", "dependencies": { @@ -11867,6 +12639,8 @@ }, "node_modules/restore-cursor": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { @@ -11882,6 +12656,8 @@ }, "node_modules/restore-cursor/node_modules/onetime": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11896,6 +12672,8 @@ }, "node_modules/restore-cursor/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -11913,6 +12691,8 @@ }, "node_modules/retry": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { @@ -11921,6 +12701,8 @@ }, "node_modules/reusify": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -11930,11 +12712,15 @@ }, "node_modules/rfdc": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, "node_modules/router": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -11949,6 +12735,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -11971,6 +12759,8 @@ }, "node_modules/run-series": { "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", "dev": true, "funding": [ { @@ -11990,6 +12780,8 @@ }, "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", @@ -12008,6 +12800,8 @@ }, "node_modules/safe-stable-stringify": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -12015,15 +12809,24 @@ }, "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/sax": { - "version": "1.4.1", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/seek-bzip": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", "dev": true, "license": "MIT", "dependencies": { @@ -12036,6 +12839,8 @@ }, "node_modules/seek-bzip/node_modules/commander": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "license": "MIT", "engines": { @@ -12044,6 +12849,8 @@ }, "node_modules/semver": { "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12054,6 +12861,8 @@ }, "node_modules/semver-regex": { "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", "engines": { @@ -12065,6 +12874,8 @@ }, "node_modules/semver-truncate": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -12078,27 +12889,35 @@ } }, "node_modules/send": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -12108,14 +12927,22 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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/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==", "dev": true, "license": "MIT", "dependencies": { @@ -12127,6 +12954,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -12135,11 +12964,15 @@ }, "node_modules/shimmer": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/side-channel": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12157,6 +12990,8 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12171,6 +13006,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12187,6 +13024,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12204,11 +13043,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==", "dev": true, "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": { @@ -12220,6 +13063,8 @@ }, "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": { @@ -12228,6 +13073,8 @@ }, "node_modules/slice-ansi": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { @@ -12243,6 +13090,8 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -12254,6 +13103,8 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { @@ -12368,6 +13219,8 @@ }, "node_modules/socks": { "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { @@ -12381,6 +13234,8 @@ }, "node_modules/socks-proxy-agent": { "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { @@ -12394,6 +13249,8 @@ }, "node_modules/sort-keys": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, "license": "MIT", "dependencies": { @@ -12405,6 +13262,8 @@ }, "node_modules/sort-keys-length": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, "license": "MIT", "dependencies": { @@ -12414,8 +13273,20 @@ "node": ">=0.10.0" } }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -12424,6 +13295,8 @@ }, "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": { @@ -12433,6 +13306,8 @@ }, "node_modules/source-map-support/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": { @@ -12441,11 +13316,15 @@ }, "node_modules/sprintf-js": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { @@ -12457,6 +13336,8 @@ }, "node_modules/stack-trace": { "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", "engines": { "node": "*" @@ -12464,6 +13345,8 @@ }, "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": { @@ -12475,6 +13358,8 @@ }, "node_modules/stack-utils/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": { @@ -12483,6 +13368,8 @@ }, "node_modules/statuses": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12507,6 +13394,8 @@ }, "node_modules/streamx": { "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { @@ -12517,6 +13406,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" @@ -12524,6 +13415,8 @@ }, "node_modules/string-argv": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", "engines": { @@ -12532,6 +13425,8 @@ }, "node_modules/string-length": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12544,6 +13439,8 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "engines": { @@ -12552,6 +13449,8 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "dependencies": { @@ -12563,6 +13462,8 @@ }, "node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -12580,6 +13481,8 @@ "node_modules/string-width-cjs": { "name": "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==", "dev": true, "license": "MIT", "dependencies": { @@ -12593,6 +13496,8 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -12601,11 +13506,15 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -12614,6 +13523,8 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -12625,6 +13536,8 @@ }, "node_modules/strip-ansi": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -12640,6 +13553,8 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -12651,6 +13566,8 @@ }, "node_modules/strip-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -12659,6 +13576,8 @@ }, "node_modules/strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -12667,6 +13586,8 @@ }, "node_modules/strip-dirs": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, "license": "ISC", "dependencies": { @@ -12674,8 +13595,20 @@ "is-plain-obj": "^1.1.0" } }, + "node_modules/strip-dirs/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -12684,6 +13617,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -12693,20 +13628,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ] - }, "node_modules/strtok3": { "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -12721,7 +13646,9 @@ } }, "node_modules/superagent": { - "version": "10.2.3", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12729,30 +13656,45 @@ "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.4", + "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.2" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" } }, "node_modules/supertest": { - "version": "7.1.4", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.2.3" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -12764,6 +13706,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "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": { @@ -12847,6 +13791,8 @@ }, "node_modules/swagger-jsdoc": { "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", "license": "MIT", "dependencies": { "commander": "6.2.0", @@ -12865,6 +13811,8 @@ }, "node_modules/swagger-jsdoc/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12873,6 +13821,8 @@ }, "node_modules/swagger-jsdoc/node_modules/commander": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", "license": "MIT", "engines": { "node": ">= 6" @@ -12880,6 +13830,9 @@ }, "node_modules/swagger-jsdoc/node_modules/glob": { "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12898,6 +13851,8 @@ }, "node_modules/swagger-jsdoc/node_modules/minimatch": { "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" @@ -12908,6 +13863,8 @@ }, "node_modules/swagger-jsdoc/node_modules/yaml": { "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "license": "ISC", "engines": { "node": ">= 6" @@ -12915,6 +13872,8 @@ }, "node_modules/swagger-parser": { "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "10.0.3" @@ -12924,7 +13883,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.30.1", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -12946,7 +13907,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12960,9 +13923,9 @@ } }, "node_modules/systeminformation": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.5.tgz", - "integrity": "sha512-DpWmpCckhwR3hG+6udb6/aQB7PpiqVnvSljrjbKxNSvTRsGsg7NVE3/vouoYf96xgwMxXFKcS4Ux+cnkFwYM7A==", + "version": "5.30.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.7.tgz", + "integrity": "sha512-33B/cftpaWdpvH+Ho9U1b08ss8GQuLxrWHelbJT1yw4M48Taj8W3ezcPuaLoIHZz5V6tVHuQPr5BprEfnBLBMw==", "dev": true, "license": "MIT", "optional": true, @@ -12988,9 +13951,9 @@ } }, "node_modules/tar": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", - "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -13006,6 +13969,8 @@ }, "node_modules/tar-stream": { "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13016,6 +13981,8 @@ }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -13024,6 +13991,8 @@ }, "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": { @@ -13037,6 +14006,8 @@ }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -13046,6 +14017,9 @@ }, "node_modules/test-exclude/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", "dev": true, "license": "ISC", "dependencies": { @@ -13065,6 +14039,8 @@ }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -13076,6 +14052,8 @@ }, "node_modules/text-decoder": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13084,10 +14062,14 @@ }, "node_modules/text-hex": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, "node_modules/through": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true, "license": "MIT" }, @@ -13104,12 +14086,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/tinyglobby": { "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13123,13 +14108,48 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -13141,17 +14161,21 @@ }, "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/token-types": { - "version": "6.1.1", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "dev": true, "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.1.0", + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -13165,6 +14189,8 @@ }, "node_modules/touch": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, "license": "ISC", "bin": { @@ -13179,13 +14205,17 @@ }, "node_modules/triple-beam": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/ts-api-utils": { - "version": "2.1.0", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -13196,7 +14226,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.5", + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", "dev": true, "license": "MIT", "dependencies": { @@ -13248,6 +14280,8 @@ }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -13259,6 +14293,8 @@ }, "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, "license": "MIT", "dependencies": { @@ -13301,6 +14337,8 @@ }, "node_modules/tsc-alias": { "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", "dev": true, "license": "MIT", "dependencies": { @@ -13319,80 +14357,20 @@ "node": ">=16.20.2" } }, - "node_modules/tsc-alias/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/tsc-alias/node_modules/commander": { "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } }, - "node_modules/tsc-alias/node_modules/glob-parent": { - "version": "5.1.2", - "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" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tsc-alias/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tsc-alias/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/tsconfig-paths": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "license": "MIT", "dependencies": { @@ -13406,6 +14384,8 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -13414,10 +14394,14 @@ }, "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==", "license": "0BSD" }, "node_modules/tv4": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", "dev": true, "license": [ { @@ -13435,6 +14419,8 @@ }, "node_modules/tx2": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", + "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", "dev": true, "license": "MIT", "optional": true, @@ -13444,6 +14430,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -13455,6 +14443,8 @@ }, "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": { @@ -13463,6 +14453,8 @@ }, "node_modules/type-fest": { "version": "0.21.3", + "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": { @@ -13474,6 +14466,8 @@ }, "node_modules/type-is": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -13492,10 +14486,14 @@ }, "node_modules/typedi": { "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -13508,6 +14506,8 @@ }, "node_modules/uglify-js": { "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -13522,6 +14522,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" }, @@ -13532,7 +14533,8 @@ "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" }, "node_modules/uint8-varint": { "version": "2.0.4", @@ -13545,9 +14547,9 @@ } }, "node_modules/uint8-varint/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/uint8-varint/node_modules/uint8arrays": { @@ -13561,6 +14563,8 @@ }, "node_modules/uint8array-extras": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "dev": true, "license": "MIT", "engines": { @@ -13580,9 +14584,9 @@ } }, "node_modules/uint8arraylist/node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/uint8arraylist/node_modules/uint8arrays": { @@ -13615,6 +14619,8 @@ }, "node_modules/unbzip2-stream": { "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "license": "MIT", "dependencies": { @@ -13622,8 +14628,35 @@ "through": "^2.3.8" } }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/undefsafe": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, @@ -13641,10 +14674,14 @@ }, "node_modules/undici-types": { "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unique-filename": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13656,6 +14693,8 @@ }, "node_modules/unique-slug": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { @@ -13667,6 +14706,8 @@ }, "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" @@ -13674,6 +14715,8 @@ }, "node_modules/unrs-resolver": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13706,7 +14749,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -13736,31 +14781,46 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, + "node_modules/utf8-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", + "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", + "license": "MIT" + }, "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/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, "license": "MIT" }, "node_modules/v8-to-istanbul": { "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": { @@ -13773,9 +14833,10 @@ } }, "node_modules/validator": { - "version": "13.15.23", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", - "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -13788,6 +14849,8 @@ }, "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==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13795,6 +14858,8 @@ }, "node_modules/vizion": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", + "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13809,6 +14874,8 @@ }, "node_modules/vizion/node_modules/async": { "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "license": "MIT", "dependencies": { @@ -13817,6 +14884,8 @@ }, "node_modules/walker": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13841,6 +14910,8 @@ }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -13854,7 +14925,9 @@ } }, "node_modules/winston": { - "version": "3.18.3", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -13875,6 +14948,8 @@ }, "node_modules/winston-daily-rotate-file": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", "license": "MIT", "dependencies": { "file-stream-rotator": "^0.6.1", @@ -13891,6 +14966,8 @@ }, "node_modules/winston-transport": { "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { "logform": "^2.7.0", @@ -13903,6 +14980,8 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -13911,11 +14990,15 @@ }, "node_modules/wordwrap": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13933,6 +15016,8 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -13949,6 +15034,8 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -13957,11 +15044,15 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -13970,6 +15061,8 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13983,6 +15076,8 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13994,6 +15089,8 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -14005,10 +15102,14 @@ }, "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==", "license": "ISC" }, "node_modules/write-file-atomic": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -14021,6 +15122,8 @@ }, "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -14032,6 +15135,8 @@ }, "node_modules/ws": { "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "license": "MIT", "engines": { @@ -14061,6 +15166,8 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" @@ -14068,11 +15175,15 @@ }, "node_modules/yallist": { "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.8.1", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", "bin": { @@ -14080,10 +15191,15 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -14100,6 +15216,8 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" @@ -14107,6 +15225,8 @@ }, "node_modules/yargs/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==", "license": "MIT", "engines": { "node": ">=8" @@ -14114,10 +15234,14 @@ }, "node_modules/yargs/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==", "license": "MIT" }, "node_modules/yargs/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==", "license": "MIT", "engines": { "node": ">=8" @@ -14125,6 +15249,8 @@ }, "node_modules/yargs/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==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14137,6 +15263,8 @@ }, "node_modules/yargs/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==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14147,6 +15275,8 @@ }, "node_modules/yauzl": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", "dev": true, "license": "MIT", "dependencies": { @@ -14159,6 +15289,8 @@ }, "node_modules/yn": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", "engines": { @@ -14167,6 +15299,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -14178,6 +15312,8 @@ }, "node_modules/z-schema": { "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", "license": "MIT", "dependencies": { "lodash.get": "^4.4.2", @@ -14196,6 +15332,8 @@ }, "node_modules/z-schema/node_modules/commander": { "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "optional": true, "engines": { diff --git a/package.json b/package.json index a6823ef..e5097b0 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", - "@types/socket.io": "^3.0.1", + "@types/socket.io": "^3.0.2", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", diff --git a/src/app.ts b/src/app.ts index 46a9889..db1ce2c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,26 +14,37 @@ import { logger, stream } from '@utils/logger'; // Google OAuth Imports import passport from 'passport'; import '@utils/passsportGoogle'; +import { createServer, Server as HttpServer } from 'http'; +import { SocketService } from '@/services/socket.service'; export class App { public app: express.Application; public env: string; public port: string | number; + public httpServer: HttpServer; + private socketService: SocketService; constructor(routes: Routes[]) { this.app = express(); this.env = NODE_ENV || 'development'; this.port = PORT || 3000; + this.httpServer = createServer(this.app); + this.initializeMiddlewares(); this.initializeRoutes(routes); this.initializeErrorHandling(); this.initializeSwagger(); + + + this.socketService = new SocketService(); + this.socketService.initialize(this.httpServer); + } public listen() { - this.app.listen(this.port, () => { + this.httpServer.listen(this.port, () => { logger.info(`=================================`); logger.info(`======= ENV: ${this.env} =======`); logger.info(`🚀 App listening on the port ${this.port}`); @@ -74,5 +85,4 @@ export class App { private initializeErrorHandling() { this.app.use(ErrorMiddleware); } -} - +} \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 2564355..cda51b5 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1,7 +1,7 @@ import prisma from '@/config/prisma'; import { DayOfWeek } from '@prisma/client'; import { AvailableDay } from '@/interfaces'; -import { Service } from 'typedi'; +import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 94bab45..8ccd670 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -3,8 +3,9 @@ import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { QueuePosition } from '@/interfaces/queue.interface'; import { DayOfWeek } from '@prisma/client'; +import { Service } from 'typedi'; - +@Service() export class QueueService { public async getQueuePosition(appointmentId: string): Promise { diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index af3a9fb..67ee521 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -1,6 +1,5 @@ import { Server as HttpServer } from 'http'; import { Server, Socket } from 'socket.io'; -import { Service } from 'typedi'; import { verify } from 'jsonwebtoken'; import { SocketStoredInToken } from '@/interfaces'; import { SECRET_KEY } from '@/config'; @@ -14,7 +13,6 @@ interface AuthenticatedSocket extends Socket { userRole?: string; } -@Service() export class SocketService { private io: Server; // userId --> set of socketIds (each tab/device = different socketId) @@ -26,12 +24,14 @@ export class SocketService { this.io = new Server(httpServer, { cors: { origin: process.env.ORIGIN, - credentials: true, + // credentials: true, // methods: ['GET', 'POST'], }, // polling is just a fallback if websocket fails transports: ['websocket', 'polling'], }); + this.io.use(this.authMiddleware.bind(this)); + this.io.on('connection', this.handleConnection.bind(this)); } public isUserConnected(userId: string): boolean { return this.userSocketMap.has(userId) && this.userSocketMap.get(userId).size > 0; @@ -90,7 +90,7 @@ export class SocketService { if (userRole === 'PATIENT') { this.sendInitialPatientData(userId); } else if (userRole === 'DOCTOR') { - // this.sendInitialDoctorData(userId); + this.sendInitialDoctorData(userId); } } @@ -108,36 +108,68 @@ export class SocketService { } } + public emitToUser(userId: string, event: string, data: any): void { + if (this.isUserConnected(userId)) { + this.io.to(`user_${userId}`).emit(event, data); + } + } + + public async emitQueueUpdatesToPatients(doctorId: string, date: Date): Promise { + const appointments = await this.getAppointmentsForDay(doctorId, date); + for (const app of appointments) { + const queuePosition = await this.queueService.getQueuePosition(app.id); + this.emitToUser(app.patient_id, 'queue_updated', queuePosition); + } + } + + private async getAppointmentsForDay(doctorId: string, date: Date): Promise<{ id: string; patient_id: string }[]> { + const startOfDay = new Date(date); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(date); + endOfDay.setHours(23, 59, 59, 999); + + + return prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay + }, + status: { in: ['CONFIRMED'] }, + deleted_at: null, + }, + select: { + id: true, + patient_id: true + + }, + }); + } + + + private async sendInitialPatientData(patientId: string): Promise { try { const appointments = await this.appointmentService.getPatientAppointments(patientId); const appointmentsWithQueue = await Promise.all(appointments.map(async (app) => { - const queuePosition = await this.queueService.calculateQueuePosition(app.id); - return { - ...app, - queuePosition, - }; + await this.queueService.calculateQueuePosition(app.id); // Ensure up-to-date + const queuePosition = await this.queueService.getQueuePosition(app.id); + return { ...app, queuePosition }; })); - this.io.to(`user_${patientId}`).emit('initial_data', { - appointments: appointmentsWithQueue, - }); - } - catch (error) { - console.error('error fetching initial patient data:', error); + this.emitToUser(patientId, 'initial_data', { appointments: appointmentsWithQueue }); + } catch (error) { + console.error('Error sending initial patient data:', error); } } private async sendInitialDoctorData(doctorId: string): Promise { - try{ + try { const schedule = await this.appointmentService.getDoctorSchedule(doctorId); - this.io.to(`user_${doctorId}`).emit('initial_data', { - schedule: schedule, - }); - } - catch (error) { - console.error('error fetching initial doctor data:', error); + this.emitToUser(doctorId, 'initial_data', { schedule }); + } catch (error) { + console.error('Error sending initial doctor data:', error); } } } - - From 1e36398ae00032e39063c46d0aad6cb2a3a7b315 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 2 Feb 2026 01:01:27 +0200 Subject: [PATCH 113/210] fix: version mismatch / connection / queue --- docker-compose.yml | 4 +- package-lock.json | 7995 +++++++++------------ package.json | 4 +- src/app.ts | 24 +- src/controllers/appointment.controller.ts | 4 +- src/controllers/queue.controller.ts | 4 +- src/routes/queue.route.ts | 39 + src/server.ts | 3 +- src/services/queue.service.ts | 10 +- src/services/socket.service.ts | 8 +- src/swagger-output.json | 67 + src/swagger.js | 7 +- 12 files changed, 3571 insertions(+), 4598 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a32ece4..89a1fb7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,8 @@ services: context: ./ dockerfile: Dockerfile ports: - - "3000:3000" - - "5555:5555" + - "3001:3000" + - "5556:5555" env_file: - .env environment: diff --git a/package-lock.json b/package-lock.json index d3b06a0..a0b4e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.3", + "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -58,7 +59,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", - "@types/socket.io": "^3.0.2", + "@types/socket.io": "^3.0.1", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", @@ -78,7 +79,6 @@ "pm2": "^6.0.13", "prettier": "^3.6.2", "supertest": "^7.1.4", - "swagger-autogen": "^2.23.7", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "tsc-alias": "^1.8.16", @@ -88,8 +88,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", - "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", @@ -100,8 +98,6 @@ }, "node_modules/@apidevtools/openapi-schemas": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", - "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "license": "MIT", "engines": { "node": ">=10" @@ -109,14 +105,10 @@ }, "node_modules/@apidevtools/swagger-methods": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", @@ -130,2862 +122,3511 @@ "openapi-types": ">=7" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "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": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "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==", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.922.0.tgz", + "integrity": "sha512-cowHCdzir4KmT/MoRyp2RV3BAebjcpiyKU1pidu2D1lI87iGXlxNG7KXJ0W8mjQoGpKa2XcihDY/mtqd/6uVlA==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/credential-provider-node": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/signature-v4-multi-region": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-sso": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.922.0.tgz", + "integrity": "sha512-jdHs7uy7cSpiMvrxhYmqHyJxgK7hyqw4plG8OQ4YTBpq0SbfAxdoOuOkwJ1IVUUQho4otR1xYYjiX/8e8J8qwQ==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@aws-sdk/core": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.922.0.tgz", + "integrity": "sha512-EvfP4cqJfpO3L2v5vkIlTkMesPtRwWlMfsaW6Tpfm7iYfBOuTi6jx60pMDMTyJNVfh6cGmXwh/kj1jQdR+w99Q==", "dev": true, - "license": "MIT", + "dependencies": { + "@aws-sdk/types": "3.922.0", + "@aws-sdk/xml-builder": "3.921.0", + "@smithy/core": "^3.17.2", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.922.0.tgz", + "integrity": "sha512-WikGQpKkROJSK3D3E7odPjZ8tU7WJp5/TgGdRuZw3izsHUeH48xMv6IznafpRTmvHcjAbDQj4U3CJZNAzOK/OQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.922.0.tgz", + "integrity": "sha512-i72DgHMK7ydAEqdzU0Duqh60Q8W59EZmRJ73y0Y5oFmNOqnYsAI+UXyOoCsubp+Dkr6+yOwAn1gPt1XGE9Aowg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-stream": "^4.5.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.922.0.tgz", + "integrity": "sha512-bVF+pI5UCLNkvbiZr/t2fgTtv84s8FCdOGAPxQiQcw5qOZywNuuCCY3wIIchmQr6GJr8YFkEp5LgDCac5EC5aQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/credential-provider-env": "3.922.0", + "@aws-sdk/credential-provider-http": "3.922.0", + "@aws-sdk/credential-provider-process": "3.922.0", + "@aws-sdk/credential-provider-sso": "3.922.0", + "@aws-sdk/credential-provider-web-identity": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.922.0.tgz", + "integrity": "sha512-agCwaD6mBihToHkjycL8ObIS2XOnWypWZZWhJSoWyHwFrhEKz1zGvgylK9Dc711oUfU+zU6J8e0JPKNJMNb3BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/credential-provider-env": "3.922.0", + "@aws-sdk/credential-provider-http": "3.922.0", + "@aws-sdk/credential-provider-ini": "3.922.0", + "@aws-sdk/credential-provider-process": "3.922.0", + "@aws-sdk/credential-provider-sso": "3.922.0", + "@aws-sdk/credential-provider-web-identity": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.922.0.tgz", + "integrity": "sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.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==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.922.0.tgz", + "integrity": "sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/client-sso": "3.922.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/token-providers": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.922.0.tgz", + "integrity": "sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.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==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.922.0.tgz", + "integrity": "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.922.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.922.0.tgz", + "integrity": "sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.922.0.tgz", + "integrity": "sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@aws-sdk/types": "3.922.0", + "@aws/lambda-invoke-store": "^0.1.1", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "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==", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.922.0.tgz", + "integrity": "sha512-ygg8lME1oFAbsH42ed2wtGqfHLoT5irgx6VC4X98j79fV1qXEwwwbqMsAiMQ/HJehpjqAFRVsHox3MHLN48Z5A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.17.2", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-stream": "^4.5.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "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==", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.922.0.tgz", + "integrity": "sha512-N4Qx/9KP3oVQBJOrSghhz8iZFtUC2NNeSZt88hpPhbqAEAtuX8aD8OzVcpnAtrwWqy82Yd2YTxlkqMGkgqnBsQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@smithy/core": "^3.17.2", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "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" + "node_modules/@aws-sdk/nested-clients": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.922.0.tgz", + "integrity": "sha512-uYvKCF1TGh/MuJ4TMqmUM0Csuao02HawcseG4LUDyxdUsd/EFuxalWq1Cx4fKZQ2K8F504efZBjctMAMNY+l7A==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/middleware-host-header": "3.922.0", + "@aws-sdk/middleware-logger": "3.922.0", + "@aws-sdk/middleware-recursion-detection": "3.922.0", + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/region-config-resolver": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@aws-sdk/util-endpoints": "3.922.0", + "@aws-sdk/util-user-agent-browser": "3.922.0", + "@aws-sdk/util-user-agent-node": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/core": "^3.17.2", + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/hash-node": "^4.2.4", + "@smithy/invalid-dependency": "^4.2.4", + "@smithy/middleware-content-length": "^4.2.4", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-retry": "^4.4.6", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.5", + "@smithy/util-defaults-mode-node": "^4.2.7", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "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==", + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.922.0.tgz", + "integrity": "sha512-44Y/rNNwhngR2KHp6gkx//TOr56/hx6s4l+XLjOqH7EBCHL7XhnrT1y92L+DLiroVr1tCSmO8eHQwBv0Y2+mvw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.922.0", + "@smithy/config-resolver": "^4.4.1", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "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==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.922.0.tgz", + "integrity": "sha512-mmsgEEL5pE+A7gFYiJMDBCLVciaXq4EFI5iAP7bPpnHvOplnNOYxVy2IreKMllGvrfjVyLnwxzZYlo5zZ65FWg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/middleware-sdk-s3": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/signature-v4": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.922.0.tgz", + "integrity": "sha512-/inmPnjZE0ZBE16zaCowAvouSx05FJ7p6BQYuzlJ8vxEU0sS0Hf8fvhuiRnN9V9eDUPIBY+/5EjbMWygXL4wlQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.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==", + "node_modules/@aws-sdk/types": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.922.0.tgz", + "integrity": "sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.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==", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.922.0.tgz", + "integrity": "sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-endpoints": "^3.2.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.922.0.tgz", + "integrity": "sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" + "@aws-sdk/types": "3.922.0", + "@smithy/types": "^4.8.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.922.0.tgz", + "integrity": "sha512-NrPe/Rsr5kcGunkog0eBV+bY0inkRELsD2SacC4lQZvZiXf8VJ2Y7j+Yq1tB+h+FPLsdt3v9wItIvDf/laAm0Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@aws-sdk/middleware-user-agent": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "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==", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.921.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.921.0.tgz", + "integrity": "sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==", "dev": true, - "license": "MIT" + "dependencies": { + "@smithy/types": "^4.8.1", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "node_modules/@aws/lambda-invoke-store": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.1.tgz", + "integrity": "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT" - }, - "node_modules/@chainsafe/netmask": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", - "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, "license": "MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/core": { + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@cspotcode/source-map-support/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==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "node_modules/@babel/generator": { + "version": "7.28.5", + "dev": true, "license": "MIT", "dependencies": { - "@so-ric/colorspace": "^1.1.6", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, "license": "MIT", "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@epic-web/invariant": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", - "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", "dev": true, "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@babel/helpers": { + "version": "7.28.4", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@babel/parser": { + "version": "7.28.5", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "*" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://eslint.org/donate" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=14" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=12.10.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.18.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@hyperledger/fabric-gateway": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.10.1.tgz", - "integrity": "sha512-nIw4oUUhHtrgxH5UAu53Dy778+xf2eM7SwvBXQlJhx9vJQ7eYX4MF0BuGGDxaYsc8gNZddddj8n99ex3Z8+exw==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.14.0", - "@hyperledger/fabric-protos": "^0.3.0", - "@noble/curves": "^1.9.4", - "google-protobuf": "^3.21.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.9.0" + "node": ">=6.9.0" }, - "optionalDependencies": { - "pkcs11js": "^2.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@hyperledger/fabric-protos": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", - "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.11.0", - "google-protobuf": "^3.21.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=16.13.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@ipld/dag-cbor": { - "version": "9.2.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", - "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "cborg": "^4.0.0", - "multiformats": "^13.1.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@ipld/dag-cbor/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@ipld/dag-json": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.6.tgz", - "integrity": "sha512-51yc5azhmkvc9mp2HV/vtJ8SlgFXADp55wAPuuAjQZ+yPurAYuTVddS3ke5vT4sjcd4DbE+DWjsMZGXjFB2cuA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", "dependencies": { - "cborg": "^4.4.0", - "multiformats": "^13.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=6.9.0" } }, - "node_modules/@ipld/dag-json/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@ipld/dag-pb": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz", - "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@babel/traverse": { + "version": "7.28.5", + "dev": true, + "license": "MIT", "dependencies": { - "multiformats": "^13.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=6.9.0" } }, - "node_modules/@ipld/dag-pb/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "node_modules/@babel/types": { + "version": "7.28.5", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "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==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@borewit/text-codec": { + "version": "0.1.1", "dev": true, "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@istanbuljs/load-nyc-config/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" - }, - "engines": { - "node": ">=8" - } + "node_modules/@chainsafe/is-ip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", + "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", + "license": "MIT" }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "@chainsafe/is-ip": "^2.0.1" } }, - "node_modules/@istanbuljs/load-nyc-config/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, + "node_modules/@colors/colors": { + "version": "1.6.0", "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, "engines": { - "node": ">=8" + "node": ">=0.1.90" } }, - "node_modules/@istanbuljs/load-nyc-config/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==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "node_modules/@epic-web/invariant": { + "version": "1.0.0", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT" }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", "dev": true, "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "node_modules/@eslint/config-array": { + "version": "0.21.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", "dev": true, "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "*" } }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" + "@eslint/core": "^0.17.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "node_modules/@eslint/core": { + "version": "0.17.0", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/get-type": "30.1.0" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", "dev": true, "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 4" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "*" } }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "node_modules/@eslint/js": { + "version": "9.39.0", "dev": true, "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=14" } }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", - "dev": true, - "license": "MIT", + "node_modules/@grpc/grpc-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", + "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "license": "Apache-2.0", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12.10.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", - "dev": true, - "license": "MIT", + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "node_modules/@humanfs/core": { + "version": "0.19.1", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.18.0" } }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "node_modules/@humanfs/node": { + "version": "0.16.7", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.18.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "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, - "license": "MIT", + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.9.0.tgz", + "integrity": "sha512-q5lFrzbKsKdMgMGhaEE4dVXtpQa4qyWMdD1RXJFki6BiiKOzZC7IEV3xj67ffSaD33iYztxomYxlHVQJqD21HQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "@hyperledger/fabric-protos": "^0.3.0", + "@noble/curves": "^1.9.4", + "google-protobuf": "^3.21.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=20.9.0" + }, + "optionalDependencies": { + "pkcs11js": "^2.1.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@libp2p/interface": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", - "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", - "license": "Apache-2.0 OR MIT", + "node_modules/@hyperledger/fabric-protos": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.3.7.tgz", + "integrity": "sha512-p69dVT+QKrL7OZOuWRrimopNUAQL+VpgVEovud5MGqHSMl20S5hZy0aWqmIW+qasRgJiHLNuU0T6xVfXJIeHKg==", + "license": "Apache-2.0", "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.1", - "main-event": "^1.0.1", - "multiformats": "^13.4.0", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" + "@grpc/grpc-js": "^1.11.0", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=16.13.0" } }, - "node_modules/@libp2p/interface-connection": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", - "integrity": "sha512-6xx/NmEc84HX7QmsjSC3hHredQYjHv4Dkf4G27adAPf+qN+vnPxmQ7gaTnk243a0++DOFTbZ2gKX/15G2B6SRg==", + "node_modules/@ipld/dag-cbor": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.5.tgz", + "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface-peer-id": "^2.0.0", - "@libp2p/interfaces": "^3.0.0", - "@multiformats/multiaddr": "^12.0.0", - "it-stream-types": "^1.0.4", - "uint8arraylist": "^2.1.2" + "cborg": "^4.0.0", + "multiformats": "^13.1.0" }, "engines": { "node": ">=16.0.0", "npm": ">=7.0.0" } }, - "node_modules/@libp2p/interface-connection/node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@libp2p/interface-connection/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", "license": "Apache-2.0 OR MIT" }, - "node_modules/@libp2p/interface-connection/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/@libp2p/interface-keychain": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface-keychain/-/interface-keychain-2.0.5.tgz", - "integrity": "sha512-mb7QNgn9fIvC7CaJCi06GJ+a6DN6RVT9TmEi0NmedZGATeCArPeWWG7r7IfxNVXb9cVOOE1RzV1swK0ZxEJF9Q==", + "node_modules/@ipld/dag-json": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-json/-/dag-json-10.2.5.tgz", + "integrity": "sha512-Q4Fr3IBDEN8gkpgNefynJ4U/ZO5Kwr7WSUMBDbZx0c37t0+IwQCTM9yJh8l5L4SRFjm31MuHwniZ/kM+P7GQ3Q==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface-peer-id": "^2.0.0", - "multiformats": "^11.0.0" + "cborg": "^4.0.0", + "multiformats": "^13.1.0" }, "engines": { "node": ">=16.0.0", "npm": ">=7.0.0" } }, - "node_modules/@libp2p/interface-peer-id": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", - "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "node_modules/@ipld/dag-json/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@ipld/dag-pb": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz", + "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "multiformats": "^11.0.0" + "multiformats": "^13.1.0" }, "engines": { "node": ">=16.0.0", "npm": ">=7.0.0" } }, - "node_modules/@libp2p/interface-peer-info": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-info/-/interface-peer-info-1.0.10.tgz", - "integrity": "sha512-HQlo8NwQjMyamCHJrnILEZz+YwEOXCB2sIIw3slIrhVUYeYlTaia1R6d9umaAeLHa255Zmdm4qGH8rJLRqhCcg==", - "license": "Apache-2.0 OR MIT", + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "@libp2p/interface-peer-id": "^2.0.0", - "@multiformats/multiaddr": "^12.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=12" } }, - "node_modules/@libp2p/interface-peer-info/node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "license": "ISC", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@libp2p/interface-peer-info/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", "dependencies": { - "multiformats": "^13.0.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/@libp2p/interface-pubsub": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@libp2p/interface-pubsub/-/interface-pubsub-3.0.7.tgz", - "integrity": "sha512-+c74EVUBTfw2sx1GE/z/IjsYO6dhur+ukF0knAppeZsRQ1Kgg6K5R3eECtT28fC6dBWLjFpAvW/7QGfiDAL4RA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@libp2p/interface-connection": "^4.0.0", - "@libp2p/interface-peer-id": "^2.0.0", - "@libp2p/interfaces": "^3.0.0", - "it-pushable": "^3.0.0", - "uint8arraylist": "^2.1.2" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=8" } }, - "node_modules/@libp2p/interface/node_modules/@multiformats/multiaddr": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", - "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@libp2p/interface/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/@libp2p/interfaces": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", - "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", - "license": "Apache-2.0 OR MIT", + "p-locate": "^4.1.0" + }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=8" } }, - "node_modules/@libp2p/logger": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", - "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "@libp2p/interface-peer-id": "^2.0.2", - "@multiformats/multiaddr": "^12.1.3", - "debug": "^4.3.4", - "interface-datastore": "^8.2.0", - "multiformats": "^11.0.2" + "p-try": "^2.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@libp2p/logger/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/@libp2p/logger/node_modules/uint8arrays/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@libp2p/peer-id": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-2.0.4.tgz", - "integrity": "sha512-gcOsN8Fbhj6izIK+ejiWsqiqKeJ2yWPapi/m55VjOvDa52/ptQzZszxQP8jUk93u36de92ATFXDfZR/Bi6eeUQ==", - "license": "Apache-2.0 OR MIT", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@libp2p/interface-peer-id": "^2.0.0", - "@libp2p/interfaces": "^3.2.0", - "multiformats": "^11.0.0", - "uint8arrays": "^4.0.2" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=8" } }, - "node_modules/@multiformats/dns": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.13.tgz", - "integrity": "sha512-yr4bxtA3MbvJ+2461kYIYMsiiZj/FIqKI64hE4SdvWJUdWF9EtZLar38juf20Sf5tguXKFUruluswAO6JsjS2w==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@multiformats/dns/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" }, - "node_modules/@multiformats/dns/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@multiformats/multiaddr": { - "version": "11.6.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-11.6.1.tgz", - "integrity": "sha512-doST0+aB7/3dGK9+U5y3mtF3jq85KGbke1QiH0KE1F5mGQ9y56mFebTeu2D9FNOm+OT6UHb8Ss8vbSnpGjeLNw==", - "license": "Apache-2.0 OR MIT", + "node_modules/@jest/console": { + "version": "30.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "dns-over-http-resolver": "^2.1.0", - "err-code": "^3.0.1", - "multiformats": "^11.0.0", - "uint8arrays": "^4.0.2", - "varint": "^6.0.0" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@multiformats/multiaddr-to-uri": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-9.0.8.tgz", - "integrity": "sha512-4eiN5iEiQfy2A98BxekUfW410L/ivg0sgjYSgSqmklnrBhK+QyMz4yqgfkub8xDTXOc7O5jp4+LVyM3ZqMeWNw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.0.0" - } - }, - "node_modules/@multiformats/multiaddr-to-uri/node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@multiformats/multiaddr-to-uri/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/@multiformats/multiaddr-to-uri/node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", - "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "node_modules/@jest/core": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, - "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.1.1", - "@napi-rs/nice-android-arm64": "1.1.1", - "@napi-rs/nice-darwin-arm64": "1.1.1", - "@napi-rs/nice-darwin-x64": "1.1.1", - "@napi-rs/nice-freebsd-x64": "1.1.1", - "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", - "@napi-rs/nice-linux-arm64-gnu": "1.1.1", - "@napi-rs/nice-linux-arm64-musl": "1.1.1", - "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", - "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", - "@napi-rs/nice-linux-s390x-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-musl": "1.1.1", - "@napi-rs/nice-openharmony-arm64": "1.1.1", - "@napi-rs/nice-win32-arm64-msvc": "1.1.1", - "@napi-rs/nice-win32-ia32-msvc": "1.1.1", - "@napi-rs/nice-win32-x64-msvc": "1.1.1" + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", - "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", - "cpu": [ - "arm" - ], + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", - "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/environment": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", - "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/expect": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", - "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", - "cpu": [ - "x64" - ], + "node_modules/@jest/expect-utils": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@jest/get-type": "30.1.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", - "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", - "cpu": [ - "x64" - ], + "node_modules/@jest/fake-timers": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", - "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", - "cpu": [ - "arm" - ], + "node_modules/@jest/get-type": { + "version": "30.1.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", - "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/globals": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", - "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/pattern": { + "version": "30.0.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", - "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", - "cpu": [ - "ppc64" - ], + "node_modules/@jest/reporters": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", - "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", - "cpu": [ - "riscv64" - ], + "node_modules/@jest/schemas": { + "version": "30.0.5", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", - "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", - "cpu": [ - "s390x" - ], + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, "engines": { - "node": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "license": "MIT" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/interface-connection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", + "integrity": "sha512-6xx/NmEc84HX7QmsjSC3hHredQYjHv4Dkf4G27adAPf+qN+vnPxmQ7gaTnk243a0++DOFTbZ2gKX/15G2B6SRg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^1.0.4", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-connection/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-keychain": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@libp2p/interface-keychain/-/interface-keychain-2.0.5.tgz", + "integrity": "sha512-mb7QNgn9fIvC7CaJCi06GJ+a6DN6RVT9TmEi0NmedZGATeCArPeWWG7r7IfxNVXb9cVOOE1RzV1swK0ZxEJF9Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-info/-/interface-peer-info-1.0.10.tgz", + "integrity": "sha512-HQlo8NwQjMyamCHJrnILEZz+YwEOXCB2sIIw3slIrhVUYeYlTaia1R6d9umaAeLHa255Zmdm4qGH8rJLRqhCcg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-pubsub": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@libp2p/interface-pubsub/-/interface-pubsub-3.0.7.tgz", + "integrity": "sha512-+c74EVUBTfw2sx1GE/z/IjsYO6dhur+ukF0knAppeZsRQ1Kgg6K5R3eECtT28fC6dBWLjFpAvW/7QGfiDAL4RA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-connection": "^4.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "it-pushable": "^3.0.0", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interfaces": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", + "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", + "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.2", + "@multiformats/multiaddr": "^12.1.3", + "debug": "^4.3.4", + "interface-datastore": "^8.2.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/peer-id": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-2.0.4.tgz", + "integrity": "sha512-gcOsN8Fbhj6izIK+ejiWsqiqKeJ2yWPapi/m55VjOvDa52/ptQzZszxQP8jUk93u36de92ATFXDfZR/Bi6eeUQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.2.0", + "multiformats": "^11.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/dns": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", + "integrity": "sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "buffer": "^6.0.3", + "dns-packet": "^5.6.1", + "hashlru": "^2.3.0", + "p-queue": "^9.0.0", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/dns/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "node_modules/@multiformats/dns/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-11.6.1.tgz", + "integrity": "sha512-doST0+aB7/3dGK9+U5y3mtF3jq85KGbke1QiH0KE1F5mGQ9y56mFebTeu2D9FNOm+OT6UHb8Ss8vbSnpGjeLNw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "dns-over-http-resolver": "^2.1.0", + "err-code": "^3.0.1", + "multiformats": "^11.0.0", + "uint8arrays": "^4.0.2", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-9.0.8.tgz", + "integrity": "sha512-4eiN5iEiQfy2A98BxekUfW410L/ivg0sgjYSgSqmklnrBhK+QyMz4yqgfkub8xDTXOc7O5jp4+LVyM3ZqMeWNw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/multiaddr-to-uri/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@pm2/agent": { + "version": "2.1.1", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.1.0", + "fclone": "~1.0.11", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.4.0", + "semver": "~7.5.0", + "ws": "~7.5.10" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "dev": true, + "license": "MIT" + }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/agent/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@pm2/blessed": { + "version": "0.1.81", + "dev": true, + "license": "MIT", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@pm2/io": { + "version": "6.1.0", + "dev": true, + "license": "Apache-2", + "dependencies": { + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@pm2/io/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@pm2/io/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@pm2/js-api": { + "version": "0.8.0", + "dev": true, + "license": "Apache-2", + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^7.0.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + } + }, + "node_modules/@prisma/client": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", + "hasInstallScript": true, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==" + }, + "node_modules/@prisma/engines": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "dependencies": { + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "dependencies": { + "@prisma/debug": "6.18.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", - "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" }, - "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", - "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" }, - "node_modules/@napi-rs/nice-openharmony-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", - "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" }, - "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", - "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" }, - "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", - "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", - "cpu": [ - "ia32" - ], + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "license": "MIT" }, - "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", - "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", - "cpu": [ - "x64" - ], + "node_modules/@sindresorhus/is": { + "version": "5.6.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", "dev": true, - "license": "MIT", - "optional": true, + "license": "BSD-3-Clause", "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "type-detect": "4.0.8" } }, - "node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@smithy/abort-controller": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.4.tgz", + "integrity": "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@smithy/config-resolver": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.1.tgz", + "integrity": "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ==", "dev": true, - "license": "MIT", + "dependencies": { + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.4", + "@smithy/util-middleware": "^4.2.4", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@smithy/core": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.2.tgz", + "integrity": "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@smithy/middleware-serde": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-stream": "^4.5.5", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.4.tgz", + "integrity": "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==", "dev": true, - "license": "ISC", "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.5.tgz", + "integrity": "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==", "dev": true, - "license": "ISC", "dependencies": { - "semver": "^7.3.5" + "@smithy/protocol-http": "^5.3.4", + "@smithy/querystring-builder": "^4.2.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18.0.0" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "node_modules/@smithy/hash-node": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.4.tgz", + "integrity": "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==", "dev": true, - "license": "MIT", "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, + "@smithy/types": "^4.8.1", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=14" + "node": ">=18.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.4.tgz", + "integrity": "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/pkgr" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", - "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "dev": true, - "license": "AGPL-3.0", "dependencies": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.1.0", - "fclone": "~1.0.11", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.4.0", - "semver": "~7.5.0", - "ws": "~7.5.10" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.4.tgz", + "integrity": "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent/node_modules/dayjs": { - "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.6.tgz", + "integrity": "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@smithy/core": "^3.17.2", + "@smithy/middleware-serde": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "@smithy/url-parser": "^4.2.4", + "@smithy/util-middleware": "^4.2.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent/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==", + "node_modules/@smithy/middleware-retry": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.6.tgz", + "integrity": "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==", "dev": true, - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "@smithy/node-config-provider": "^4.3.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/service-error-classification": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-retry": "^4.2.4", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent/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==", + "node_modules/@smithy/middleware-serde": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.4.tgz", + "integrity": "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==", "dev": true, - "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/agent/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pm2/blessed": { - "version": "0.1.81", - "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", + "node_modules/@smithy/middleware-stack": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.4.tgz", + "integrity": "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==", "dev": true, - "license": "MIT", - "bin": { - "blessed": "bin/tput.js" + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", - "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", + "node_modules/@smithy/node-config-provider": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.4.tgz", + "integrity": "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==", "dev": true, - "license": "Apache-2", "dependencies": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@smithy/node-http-handler": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.4.tgz", + "integrity": "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==", "dev": true, - "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "@smithy/abort-controller": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/querystring-builder": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/@smithy/property-provider": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.4.tgz", + "integrity": "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pm2/io/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==", + "node_modules/@smithy/protocol-http": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.4.tgz", + "integrity": "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==", "dev": true, - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/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==", + "node_modules/@smithy/querystring-builder": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.4.tgz", + "integrity": "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==", "dev": true, - "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@smithy/types": "^4.8.1", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@pm2/io/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/@smithy/querystring-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.4.tgz", + "integrity": "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==", "dev": true, - "license": "ISC" + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pm2/js-api": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", - "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "node_modules/@smithy/service-error-classification": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.4.tgz", + "integrity": "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==", "dev": true, - "license": "Apache-2", "dependencies": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" + "@smithy/types": "^4.8.1" }, "engines": { - "node": ">=4.0" + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.4.tgz", + "integrity": "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==", "dev": true, - "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/@smithy/signature-v4": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.4.tgz", + "integrity": "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.4", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "node_modules/@smithy/smithy-client": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.2.tgz", + "integrity": "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==", "dev": true, - "license": "MIT" + "dependencies": { + "@smithy/core": "^3.17.2", + "@smithy/middleware-endpoint": "^4.3.6", + "@smithy/middleware-stack": "^4.2.4", + "@smithy/protocol-http": "^5.3.4", + "@smithy/types": "^4.8.1", + "@smithy/util-stream": "^4.5.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "node_modules/@smithy/types": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.1.tgz", + "integrity": "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "^4.3.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/client": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", - "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" + "node_modules/@smithy/url-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.4.tgz", + "integrity": "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==", + "dev": true, + "dependencies": { + "@smithy/querystring-parser": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/config": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", - "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", - "license": "Apache-2.0", + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dev": true, "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/debug": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", - "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", - "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/fetch-engine": "6.18.0", - "@prisma/get-platform": "6.18.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/engines-version": { - "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", - "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", - "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", - "license": "Apache-2.0", + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/get-platform": "6.18.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/get-platform": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", - "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", - "license": "Apache-2.0", + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dev": true, "dependencies": { - "@prisma/debug": "6.18.0" + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dev": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.5.tgz", + "integrity": "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==", + "dev": true, + "dependencies": { + "@smithy/property-provider": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.7.tgz", + "integrity": "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw==", + "dev": true, + "dependencies": { + "@smithy/config-resolver": "^4.4.1", + "@smithy/credential-provider-imds": "^4.2.4", + "@smithy/node-config-provider": "^4.3.4", + "@smithy/property-provider": "^4.2.4", + "@smithy/smithy-client": "^4.9.2", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "node_modules/@smithy/util-endpoints": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.4.tgz", + "integrity": "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" + "node_modules/@smithy/util-middleware": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.4.tgz", + "integrity": "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==", + "dev": true, + "dependencies": { + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "node_modules/@smithy/util-retry": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.4.tgz", + "integrity": "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==", "dev": true, - "license": "MIT" + "dependencies": { + "@smithy/service-error-classification": "^4.2.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "node_modules/@smithy/util-stream": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.5.tgz", + "integrity": "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==", "dev": true, - "license": "MIT", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.5", + "@smithy/node-http-handler": "^4.4.4", + "@smithy/types": "^4.8.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=14.16" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "type-detect": "4.0.8" + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", - "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", "license": "MIT", "dependencies": { "color": "^5.0.2", @@ -2999,15 +3640,12 @@ "license": "MIT" }, "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" }, "node_modules/@swc/cli": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.10.tgz", - "integrity": "sha512-QQ36Q1VwGTT2YzvMeNe/j1x4DKS277DscNhWc57dIwQn//C+zAgvuSupMB/XkmYqPKQX+8hjn5/cHRJrMvWy0Q==", + "version": "0.7.8", "dev": true, "license": "MIT", "dependencies": { @@ -3040,9 +3678,7 @@ } }, "node_modules/@swc/core": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", - "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", + "version": "1.14.0", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -3058,16 +3694,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.11", - "@swc/core-darwin-x64": "1.15.11", - "@swc/core-linux-arm-gnueabihf": "1.15.11", - "@swc/core-linux-arm64-gnu": "1.15.11", - "@swc/core-linux-arm64-musl": "1.15.11", - "@swc/core-linux-x64-gnu": "1.15.11", - "@swc/core-linux-x64-musl": "1.15.11", - "@swc/core-win32-arm64-msvc": "1.15.11", - "@swc/core-win32-ia32-msvc": "1.15.11", - "@swc/core-win32-x64-msvc": "1.15.11" + "@swc/core-darwin-arm64": "1.14.0", + "@swc/core-darwin-x64": "1.14.0", + "@swc/core-linux-arm-gnueabihf": "1.14.0", + "@swc/core-linux-arm64-gnu": "1.14.0", + "@swc/core-linux-arm64-musl": "1.14.0", + "@swc/core-linux-x64-gnu": "1.14.0", + "@swc/core-linux-x64-musl": "1.14.0", + "@swc/core-win32-arm64-msvc": "1.14.0", + "@swc/core-win32-ia32-msvc": "1.14.0", + "@swc/core-win32-x64-msvc": "1.14.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3078,112 +3714,8 @@ } } }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", - "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", - "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", - "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", - "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", - "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", - "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", - "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", + "version": "1.14.0", "cpu": [ "x64" ], @@ -3197,68 +3729,13 @@ "node": ">=10" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", - "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", - "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", - "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3267,8 +3744,6 @@ }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "license": "MIT", "dependencies": { @@ -3280,8 +3755,6 @@ }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", "dev": true, "license": "MIT", "dependencies": { @@ -3299,61 +3772,36 @@ }, "node_modules/@tokenizer/token": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "dev": true, "license": "MIT" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "version": "1.0.11", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, "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, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/babel__core": { "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": { @@ -3366,8 +3814,6 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -3376,8 +3822,6 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", - "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": { @@ -3387,8 +3831,6 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3397,8 +3839,6 @@ }, "node_modules/@types/bcrypt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3407,8 +3847,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -3418,8 +3856,6 @@ }, "node_modules/@types/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3429,8 +3865,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -3439,8 +3873,6 @@ }, "node_modules/@types/cookie-parser": { "version": "1.4.10", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", - "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3449,15 +3881,11 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -3465,27 +3893,21 @@ }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "version": "5.0.5", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "version": "5.1.0", "dev": true, "license": "MIT", "dependencies": { @@ -3500,15 +3922,12 @@ "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", "dev": true, - "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/hpp": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.7.tgz", - "integrity": "sha512-YSQBkTwZepklRez0wgsljeewMytGNKgBAZR1YbmE0X49+elqkZ+fr/gvB407wL9Dl7a/Kv3W04yJueRmEHytBw==", "dev": true, "license": "MIT", "dependencies": { @@ -3516,30 +3935,22 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "version": "4.0.4", "dev": true, "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "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, "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": { @@ -3548,8 +3959,6 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "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": { @@ -3558,8 +3967,6 @@ }, "node_modules/@types/jest": { "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { @@ -3569,14 +3976,10 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { @@ -3586,8 +3989,11 @@ }, "node_modules/@types/methods": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", "dev": true, "license": "MIT" }, @@ -3599,8 +4005,6 @@ }, "node_modules/@types/morgan": { "version": "1.9.10", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", - "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", "dev": true, "license": "MIT", "dependencies": { @@ -3609,8 +4013,6 @@ }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, @@ -3625,21 +4027,21 @@ } }, "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, "node_modules/@types/nodemailer": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", - "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-fC8w49YQ868IuPWRXqPfLf+MuTRex5Z1qxMoG8rr70riqqbOp2F5xgOKE9fODEBPzpnvjkJXFgK6IL2xgMSTnA==", "dev": true, - "license": "MIT", "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, @@ -3648,7 +4050,6 @@ "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3658,7 +4059,6 @@ "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", "dev": true, - "license": "MIT", "dependencies": { "@types/express": "*" } @@ -3668,7 +4068,6 @@ "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.17.tgz", "integrity": "sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/express": "*", "@types/passport": "*", @@ -3680,7 +4079,6 @@ "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/express": "*", "@types/oauth": "*", @@ -3689,44 +4087,45 @@ }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "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==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "node_modules/@types/serve-static": { + "version": "1.15.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", "dev": true, "license": "MIT", "dependencies": { - "@types/http-errors": "*", + "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/socket.io": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", - "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", - "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.1.tgz", + "integrity": "sha512-XSma2FhVD78ymvoxYV4xGXrIH/0EKQ93rR+YR0Y+Kw1xbPzLDCip/UWSejZ08FpxYeYNci/PZPQS9anrvJRqMA==", "dev": true, "license": "MIT", "dependencies": { @@ -3735,15 +4134,11 @@ }, "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, "license": "MIT" }, "node_modules/@types/superagent": { "version": "8.1.9", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", - "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3755,8 +4150,6 @@ }, "node_modules/@types/supertest": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", - "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "license": "MIT", "dependencies": { @@ -3766,15 +4159,11 @@ }, "node_modules/@types/swagger-jsdoc": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", - "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", "dev": true, "license": "MIT" }, "node_modules/@types/swagger-ui-express": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", - "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", "dev": true, "license": "MIT", "dependencies": { @@ -3784,20 +4173,14 @@ }, "node_modules/@types/triple-beam": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, "node_modules/@types/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "version": "13.15.4", "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "version": "17.0.34", "dev": true, "license": "MIT", "dependencies": { @@ -3806,26 +4189,23 @@ }, "node_modules/@types/yargs-parser": { "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, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "ignore": "^7.0.5", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3835,23 +4215,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.46.2", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3" + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3866,15 +4244,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", - "debug": "^4.4.3" + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3888,14 +4264,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3906,9 +4280,7 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.46.2", "dev": true, "license": "MIT", "engines": { @@ -3923,17 +4295,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3948,9 +4318,7 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.46.2", "dev": true, "license": "MIT", "engines": { @@ -3962,21 +4330,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3990,16 +4357,14 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4014,13 +4379,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.46.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4033,8 +4396,6 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4046,211 +4407,11 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, "license": "ISC" }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -4261,69 +4422,8 @@ "linux" ] }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@xhmikosr/archive-type": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", - "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", "dev": true, "license": "MIT", "dependencies": { @@ -4335,8 +4435,6 @@ }, "node_modules/@xhmikosr/bin-check": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", - "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4349,8 +4447,6 @@ }, "node_modules/@xhmikosr/bin-wrapper": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", - "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4365,8 +4461,6 @@ }, "node_modules/@xhmikosr/decompress": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", - "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", "dev": true, "license": "MIT", "dependencies": { @@ -4383,8 +4477,6 @@ }, "node_modules/@xhmikosr/decompress-tar": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", - "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -4398,8 +4490,6 @@ }, "node_modules/@xhmikosr/decompress-tarbz2": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", - "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", "dev": true, "license": "MIT", "dependencies": { @@ -4415,8 +4505,6 @@ }, "node_modules/@xhmikosr/decompress-targz": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", - "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", "dev": true, "license": "MIT", "dependencies": { @@ -4430,8 +4518,6 @@ }, "node_modules/@xhmikosr/decompress-unzip": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", - "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", "dev": true, "license": "MIT", "dependencies": { @@ -4445,8 +4531,6 @@ }, "node_modules/@xhmikosr/downloader": { "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", - "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", "dev": true, "license": "MIT", "dependencies": { @@ -4466,8 +4550,6 @@ }, "node_modules/@xhmikosr/os-filter-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", - "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", "dev": true, "license": "MIT", "dependencies": { @@ -4479,8 +4561,6 @@ }, "node_modules/abbrev": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { @@ -4495,8 +4575,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -4508,8 +4586,6 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4517,8 +4593,6 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -4530,8 +4604,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4540,8 +4612,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -4553,8 +4623,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -4563,8 +4631,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -4580,15 +4646,11 @@ }, "node_modules/amp": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", - "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", "dev": true, "license": "MIT" }, "node_modules/amp-message": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", - "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", "dev": true, "license": "MIT", "dependencies": { @@ -4597,8 +4659,6 @@ }, "node_modules/ansi-colors": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", "engines": { @@ -4607,8 +4667,6 @@ }, "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": { @@ -4623,8 +4681,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -4636,8 +4692,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4651,8 +4705,6 @@ }, "node_modules/ansis": { "version": "4.0.0-node10", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", - "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "dev": true, "license": "ISC", "engines": { @@ -4667,8 +4719,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -4679,6 +4729,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4687,8 +4750,6 @@ }, "node_modules/arch": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", - "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", "dev": true, "funding": [ { @@ -4708,21 +4769,15 @@ }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { @@ -4731,15 +4786,11 @@ }, "node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "node_modules/ast-types": { "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4751,21 +4802,15 @@ }, "node_modules/async": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "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, "license": "MIT" }, "node_modules/b4a": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4779,8 +4824,6 @@ }, "node_modules/babel-jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { @@ -4801,8 +4844,6 @@ }, "node_modules/babel-plugin-istanbul": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -4821,8 +4862,6 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { @@ -4834,8 +4873,6 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -4861,8 +4898,6 @@ }, "node_modules/babel-preset-jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4878,14 +4913,10 @@ }, "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==", "license": "MIT" }, "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.8.1", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4899,8 +4930,6 @@ }, "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", @@ -4930,15 +4959,12 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", - "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.8.22", "dev": true, "license": "Apache-2.0", "bin": { @@ -4947,8 +4973,6 @@ }, "node_modules/basic-auth": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" @@ -4959,14 +4983,10 @@ }, "node_modules/basic-auth/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==", "license": "MIT" }, "node_modules/basic-ftp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", - "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "version": "5.0.5", "dev": true, "license": "MIT", "engines": { @@ -4975,8 +4995,6 @@ }, "node_modules/bcrypt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4989,8 +5007,6 @@ }, "node_modules/bin-version": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", - "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", "dev": true, "license": "MIT", "dependencies": { @@ -5006,8 +5022,6 @@ }, "node_modules/bin-version-check": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", - "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -5046,15 +5060,13 @@ }, "node_modules/bodec": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", "dev": true, "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -5063,7 +5075,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.1", + "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -5075,10 +5087,30 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "dev": true + }, "node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5087,8 +5119,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -5105,9 +5135,7 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.27.0", "dev": true, "funding": [ { @@ -5125,11 +5153,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -5140,8 +5168,6 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", "dependencies": { @@ -5153,8 +5179,6 @@ }, "node_modules/bser": { "version": "2.1.1", - "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": { @@ -5162,9 +5186,8 @@ } }, "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "version": "5.7.1", + "dev": true, "funding": [ { "type": "github", @@ -5182,13 +5205,11 @@ "license": "MIT", "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "ieee754": "^1.1.13" } }, "node_modules/buffer-crc32": { "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { @@ -5197,14 +5218,10 @@ }, "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==", "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==", "license": "MIT" }, "node_modules/busboy": { @@ -5220,8 +5237,6 @@ }, "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" @@ -5231,7 +5246,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "license": "MIT", "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", @@ -5249,32 +5263,16 @@ "peerDependencies": { "magicast": "^0.3.5" }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } - } - }, - "node_modules/c12/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -5282,23 +5280,8 @@ "url": "https://dotenvx.com" } }, - "node_modules/c12/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/cacache": { "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { @@ -5321,15 +5304,11 @@ }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/cacheable-lookup": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, "license": "MIT", "engines": { @@ -5338,8 +5317,6 @@ }, "node_modules/cacheable-request": { "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5357,8 +5334,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5370,8 +5345,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5386,14 +5359,10 @@ }, "node_modules/call-me-maybe": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -5402,8 +5371,6 @@ }, "node_modules/camelcase": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -5411,9 +5378,7 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001752", "dev": true, "funding": [ { @@ -5432,9 +5397,9 @@ "license": "CC-BY-4.0" }, "node_modules/cborg": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.5.8.tgz", - "integrity": "sha512-6/viltD51JklRhq4L7jC3zgy6gryuG5xfZ3kzpE+PravtyeQLeQmCYLREhQH7pWENg5pY4Yu/XCd6a7dKScVlw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.3.0.tgz", + "integrity": "sha512-vOXo1pB4mdeBw3LbpoynQlZNw/H3kZVHLtPYlp8kFMreL/2YfT54F70BM1s3iDoCtQ+3C9QmiRF4rfCSSTlhBw==", "license": "Apache-2.0", "bin": { "cborg": "lib/bin.js" @@ -5442,8 +5407,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -5459,8 +5422,6 @@ }, "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": { @@ -5469,53 +5430,26 @@ }, "node_modules/charm": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", "dev": true, "license": "MIT/X11" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "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" - }, - "engines": { - "node": ">= 6" } }, "node_modules/chownr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -5523,9 +5457,7 @@ } }, "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "version": "4.3.1", "dev": true, "funding": [ { @@ -5542,39 +5474,30 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "license": "MIT", "dependencies": { "consola": "^3.2.3" } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.1.0", "dev": true, "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "license": "MIT" }, "node_modules/class-validator": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", - "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", + "version": "0.14.2", "license": "MIT", "dependencies": { - "@types/validator": "^13.15.3", + "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", - "validator": "^13.15.20" + "validator": "^13.9.0" } }, "node_modules/cli-cursor": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { @@ -5589,8 +5512,6 @@ }, "node_modules/cli-tableau": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", - "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", "dev": true, "dependencies": { "chalk": "3.0.0" @@ -5601,8 +5522,6 @@ }, "node_modules/cli-tableau/node_modules/chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "license": "MIT", "dependencies": { @@ -5615,8 +5534,6 @@ }, "node_modules/cli-truncate": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { @@ -5631,9 +5548,7 @@ } }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", - "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "version": "8.1.0", "dev": true, "license": "MIT", "dependencies": { @@ -5649,8 +5564,6 @@ }, "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==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5663,8 +5576,6 @@ }, "node_modules/cliui/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==", "license": "MIT", "engines": { "node": ">=8" @@ -5672,14 +5583,10 @@ }, "node_modules/cliui/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==", "license": "MIT" }, "node_modules/cliui/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==", "license": "MIT", "engines": { "node": ">=8" @@ -5687,8 +5594,6 @@ }, "node_modules/cliui/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==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5701,8 +5606,6 @@ }, "node_modules/cliui/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==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5713,8 +5616,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5729,12 +5630,13 @@ } }, "node_modules/cloudinary": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.9.0.tgz", - "integrity": "sha512-F3iKMOy4y0zy0bi5JBp94SC7HY7i/ImfTPSUV07iJmRzH1Iz8WavFfOlJTR1zvYM/xKGoiGZ3my/zy64In0IQQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz", + "integrity": "sha512-s7frvR0HnQXeJsQSIsbLa/I09IMb1lOnVLEDH5b5E53WTiCYgrNNOBGV/i/nLHwrcEOUkqjfSwP1+enXWNYmdw==", "license": "MIT", "dependencies": { - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "q": "^1.5.1" }, "engines": { "node": ">=9" @@ -5742,8 +5644,6 @@ }, "node_modules/co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -5753,19 +5653,15 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, "node_modules/color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "version": "5.0.2", "license": "MIT", "dependencies": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" + "color-convert": "^3.0.1", + "color-string": "^2.0.0" }, "engines": { "node": ">=18" @@ -5773,8 +5669,6 @@ }, "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==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5785,14 +5679,10 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "version": "2.1.2", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5802,18 +5692,14 @@ } }, "node_modules/color-string/node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "version": "2.0.2", "license": "MIT", "engines": { "node": ">=12.20" } }, "node_modules/color/node_modules/color-convert": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", - "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "version": "3.1.2", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -5823,9 +5709,7 @@ } }, "node_modules/color/node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "version": "2.0.2", "license": "MIT", "engines": { "node": ">=12.20" @@ -5833,15 +5717,11 @@ }, "node_modules/colorette": { "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5853,8 +5733,6 @@ }, "node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "license": "MIT", "engines": { @@ -5863,8 +5741,6 @@ }, "node_modules/component-emitter": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -5873,8 +5749,6 @@ }, "node_modules/compressible": { "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -5885,8 +5759,6 @@ }, "node_modules/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -5903,8 +5775,6 @@ }, "node_modules/compression/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" @@ -5912,14 +5782,10 @@ }, "node_modules/compression/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/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, "node_modules/concat-stream": { @@ -5940,22 +5806,18 @@ "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "license": "MIT" + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -5967,8 +5829,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" @@ -5976,15 +5836,11 @@ }, "node_modules/convert-source-map": { "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, "license": "MIT" }, "node_modules/cookie": { "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" @@ -5992,8 +5848,6 @@ }, "node_modules/cookie-parser": { "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { "cookie": "0.7.2", @@ -6005,21 +5859,15 @@ }, "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/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, "license": "MIT" }, "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "version": "2.8.5", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -6027,30 +5875,20 @@ }, "engines": { "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "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, "license": "MIT" }, "node_modules/croner": { "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", "dev": true, "license": "MIT" }, "node_modules/cross-env": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", - "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, "license": "MIT", "dependencies": { @@ -6067,8 +5905,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -6082,8 +5918,6 @@ }, "node_modules/culvert": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", "dev": true, "license": "MIT" }, @@ -6099,8 +5933,6 @@ }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "license": "MIT", "engines": { @@ -6109,15 +5941,11 @@ }, "node_modules/dayjs": { "version": "1.11.15", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", - "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -6133,8 +5961,6 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6149,8 +5975,6 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", "engines": { @@ -6161,9 +5985,7 @@ } }, "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "version": "1.7.0", "dev": true, "license": "MIT", "peerDependencies": { @@ -6177,16 +5999,11 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6196,15 +6013,12 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" } }, "node_modules/defaults": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", - "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", "dev": true, "license": "MIT", "engines": { @@ -6216,8 +6030,6 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, "license": "MIT", "engines": { @@ -6227,13 +6039,10 @@ "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" }, "node_modules/degenerator": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6247,8 +6056,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { @@ -6257,8 +6064,6 @@ }, "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" @@ -6267,13 +6072,10 @@ "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==" }, "node_modules/detect-newline": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -6282,8 +6084,6 @@ }, "node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "license": "ISC", "dependencies": { @@ -6303,8 +6103,6 @@ }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -6326,10 +6124,20 @@ "undici": "^5.12.0" } }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -6340,8 +6148,6 @@ }, "node_modules/dotenv": { "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -6355,7 +6161,6 @@ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", "dev": true, - "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", "dotenv": "^17.1.0", @@ -6371,7 +6176,6 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" }, @@ -6387,7 +6191,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -6397,8 +6200,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6411,15 +6212,11 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "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" @@ -6427,15 +6224,12 @@ }, "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/effect": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" @@ -6454,16 +6248,12 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.283", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", - "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "version": "1.5.244", "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": { @@ -6475,8 +6265,6 @@ }, "node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -6484,21 +6272,16 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/enabled": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "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" @@ -6506,25 +6289,11 @@ }, "node_modules/encoding": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/engine.io": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", @@ -6620,8 +6389,6 @@ }, "node_modules/enquirer": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "license": "MIT", "dependencies": { @@ -6633,8 +6400,6 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -6642,9 +6407,7 @@ } }, "node_modules/envalid": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.1.tgz", - "integrity": "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w==", + "version": "8.1.0", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -6655,8 +6418,6 @@ }, "node_modules/environment": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "engines": { @@ -6667,15 +6428,12 @@ } }, "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "version": "2.0.3", + "dev": true, "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6684,8 +6442,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6693,8 +6449,6 @@ }, "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" @@ -6702,8 +6456,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6714,8 +6466,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -6730,8 +6480,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -6739,14 +6487,10 @@ }, "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/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -6758,8 +6502,6 @@ }, "node_modules/escodegen": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6780,8 +6522,6 @@ }, "node_modules/escodegen/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", "optional": true, @@ -6790,9 +6530,7 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.0", "dev": true, "license": "MIT", "dependencies": { @@ -6802,7 +6540,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/js": "9.39.0", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6851,8 +6589,6 @@ }, "node_modules/eslint-config-prettier": { "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { @@ -6866,14 +6602,12 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", - "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "version": "5.5.4", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -6898,8 +6632,6 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6915,8 +6647,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6928,8 +6658,6 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -6939,8 +6667,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6952,8 +6678,6 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -6962,8 +6686,6 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -6975,8 +6697,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6993,8 +6713,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7006,8 +6724,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "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": { @@ -7019,9 +6735,7 @@ } }, "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "version": "1.6.0", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7033,8 +6747,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7046,8 +6758,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -7056,8 +6766,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -7065,8 +6773,6 @@ }, "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" @@ -7074,21 +6780,15 @@ }, "node_modules/eventemitter2": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", - "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", "dev": true, "license": "MIT" }, "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "version": "5.0.1", "license": "MIT" }, "node_modules/events-universal": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7097,8 +6797,6 @@ }, "node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -7121,8 +6819,6 @@ }, "node_modules/exit-x": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, "license": "MIT", "engines": { @@ -7131,8 +6827,6 @@ }, "node_modules/expect": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -7149,25 +6843,20 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "version": "5.1.0", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.1", + "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", - "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -7198,39 +6887,32 @@ } }, "node_modules/express-session": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", - "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", - "license": "MIT", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "dependencies": { - "cookie": "~0.7.2", - "cookie-signature": "~1.0.7", - "debug": "~2.6.9", + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", - "safe-buffer": "~5.2.1", + "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, "node_modules/express-session/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" } @@ -7238,26 +6920,20 @@ "node_modules/express-session/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.0.0", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.6" } }, "node_modules/express/node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -7266,13 +6942,10 @@ "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==" }, "node_modules/ext-list": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "license": "MIT", "dependencies": { @@ -7284,8 +6957,6 @@ }, "node_modules/ext-name": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7298,8 +6969,6 @@ }, "node_modules/extrareqp2": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", - "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", "dev": true, "license": "MIT", "dependencies": { @@ -7320,7 +6989,6 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", "dependencies": { "pure-rand": "^6.1.0" }, @@ -7341,33 +7009,24 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "license": "MIT" + ] }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, "node_modules/fast-fifo": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -7383,8 +7042,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -7396,36 +7053,44 @@ }, "node_modules/fast-json-patch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "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, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.19.1", "dev": true, "license": "ISC", "dependencies": { @@ -7434,8 +7099,6 @@ }, "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": { @@ -7444,28 +7107,36 @@ }, "node_modules/fclone": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fecha": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, "node_modules/fflate": { "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7477,8 +7148,6 @@ }, "node_modules/file-stream-rotator": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", - "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", "license": "MIT", "dependencies": { "moment": "^2.29.1" @@ -7486,8 +7155,6 @@ }, "node_modules/file-type": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", - "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", "dev": true, "license": "MIT", "dependencies": { @@ -7505,8 +7172,6 @@ }, "node_modules/filename-reserved-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", - "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, "license": "MIT", "engines": { @@ -7518,8 +7183,6 @@ }, "node_modules/filenamify": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", - "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7534,8 +7197,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -7546,9 +7207,7 @@ } }, "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "version": "2.1.0", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -7559,17 +7218,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8" } }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -7585,8 +7238,6 @@ }, "node_modules/find-versions": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", - "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -7601,8 +7252,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -7615,21 +7264,15 @@ }, "node_modules/flatted": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -7649,8 +7292,6 @@ }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -7666,8 +7307,6 @@ }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -7678,9 +7317,7 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.4", "dev": true, "license": "MIT", "dependencies": { @@ -7696,8 +7333,6 @@ }, "node_modules/form-data-encoder": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true, "license": "MIT", "engines": { @@ -7706,8 +7341,6 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "engines": { @@ -7716,8 +7349,6 @@ }, "node_modules/form-data/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==", "dev": true, "license": "MIT", "dependencies": { @@ -7729,8 +7360,6 @@ }, "node_modules/formidable": { "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { @@ -7747,8 +7376,6 @@ }, "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" @@ -7756,8 +7383,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7765,8 +7390,6 @@ }, "node_modules/fs-minipass": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, "license": "ISC", "dependencies": { @@ -7778,8 +7401,6 @@ }, "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==", "license": "ISC" }, "node_modules/fsevents": { @@ -7799,8 +7420,6 @@ }, "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" @@ -7808,8 +7427,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "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": { @@ -7818,8 +7435,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7827,8 +7442,6 @@ }, "node_modules/get-east-asian-width": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, "license": "MIT", "engines": { @@ -7840,8 +7453,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7870,8 +7481,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -7880,8 +7489,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7893,8 +7500,6 @@ }, "node_modules/get-stream": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -7905,9 +7510,7 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", - "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", + "version": "4.13.0", "dev": true, "license": "MIT", "dependencies": { @@ -7919,8 +7522,6 @@ }, "node_modules/get-uri": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -7936,7 +7537,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "license": "MIT", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -7951,15 +7551,11 @@ }, "node_modules/git-node-fs": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", "dev": true, "license": "MIT" }, "node_modules/git-sha1": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", "dev": true, "license": "MIT" }, @@ -7986,8 +7582,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -7999,8 +7593,6 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -8012,8 +7604,6 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", "dependencies": { @@ -8033,8 +7623,6 @@ }, "node_modules/globby/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -8049,8 +7637,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8061,8 +7647,6 @@ }, "node_modules/got": { "version": "13.0.0", - "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", - "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, "license": "MIT", "dependencies": { @@ -8087,15 +7671,16 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8116,8 +7701,6 @@ }, "node_modules/handlebars/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": { @@ -8126,8 +7709,6 @@ }, "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, "license": "MIT", "engines": { @@ -8136,8 +7717,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8148,8 +7727,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -8170,8 +7747,6 @@ }, "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" @@ -8182,8 +7757,6 @@ }, "node_modules/helmet": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -8191,8 +7764,6 @@ }, "node_modules/hpp": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", - "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", "license": "ISC", "dependencies": { "lodash": "^4.17.12", @@ -8204,8 +7775,6 @@ }, "node_modules/hpp/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" @@ -8213,8 +7782,6 @@ }, "node_modules/hpp/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==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8222,8 +7789,6 @@ }, "node_modules/hpp/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==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8234,8 +7799,6 @@ }, "node_modules/hpp/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", @@ -8247,42 +7810,37 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-cache-semantics": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "version": "2.0.0", "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -8295,8 +7853,6 @@ }, "node_modules/http2-wrapper": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8309,8 +7865,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -8323,8 +7877,6 @@ }, "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": { @@ -8333,8 +7885,6 @@ }, "node_modules/husky": { "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -8348,25 +7898,17 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.6.3", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "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", @@ -8385,8 +7927,6 @@ }, "node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -8395,15 +7935,11 @@ }, "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, "license": "ISC" }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8419,8 +7955,6 @@ }, "node_modules/import-local": { "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": { @@ -8439,8 +7973,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -8449,9 +7981,6 @@ }, "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.", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -8460,21 +7989,15 @@ }, "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/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, "node_modules/inspect-with-kind": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", - "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", "dev": true, "license": "ISC", "dependencies": { @@ -8492,9 +8015,9 @@ } }, "node_modules/interface-datastore/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", "license": "Apache-2.0 OR MIT" }, "node_modules/interface-datastore/node_modules/uint8arrays": { @@ -8513,9 +8036,7 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.0.1", "dev": true, "license": "MIT", "engines": { @@ -8524,8 +8045,6 @@ }, "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" @@ -8627,6 +8146,12 @@ "npm": ">=7.0.0" } }, + "node_modules/ipfs-core-utils/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/ipfs-http-client": { "version": "60.0.1", "resolved": "https://registry.npmjs.org/ipfs-http-client/-/ipfs-http-client-60.0.1.tgz", @@ -8659,6 +8184,12 @@ "npm": ">=7.0.0" } }, + "node_modules/ipfs-http-client/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/ipfs-unixfs": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-9.0.1.tgz", @@ -8673,6 +8204,12 @@ "npm": ">=7.0.0" } }, + "node_modules/ipfs-unixfs/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/ipfs-utils": { "version": "9.0.14", "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-9.0.14.tgz", @@ -8707,6 +8244,36 @@ "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", "license": "ISC" }, + "node_modules/ipfs-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "node_modules/ipfs-utils/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/ipfs-utils/node_modules/it-all": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", @@ -8742,8 +8309,6 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, @@ -8762,8 +8327,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -8784,8 +8347,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -8794,8 +8355,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8810,8 +8369,6 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", - "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": { @@ -8820,8 +8377,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -8833,8 +8388,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -8842,24 +8395,19 @@ } }, "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "version": "1.1.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -8870,8 +8418,6 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, @@ -8886,8 +8432,6 @@ }, "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": { @@ -8896,8 +8440,6 @@ }, "node_modules/istanbul-lib-instrument": { "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": { @@ -8913,8 +8455,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "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": { @@ -8928,8 +8468,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8943,8 +8481,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9070,6 +8606,30 @@ "readable-stream": "^3.6.0" } }, + "node_modules/it-to-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, "node_modules/it-to-stream/node_modules/p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -9081,8 +8641,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -9097,8 +8655,6 @@ }, "node_modules/jest": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { @@ -9124,8 +8680,6 @@ }, "node_modules/jest-changed-files": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9139,8 +8693,6 @@ }, "node_modules/jest-circus": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { @@ -9171,8 +8723,6 @@ }, "node_modules/jest-cli": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { @@ -9204,8 +8754,6 @@ }, "node_modules/jest-config": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { @@ -9256,8 +8804,6 @@ }, "node_modules/jest-diff": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { @@ -9272,8 +8818,6 @@ }, "node_modules/jest-docblock": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -9285,8 +8829,6 @@ }, "node_modules/jest-each": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9302,8 +8844,6 @@ }, "node_modules/jest-environment-node": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { @@ -9321,8 +8861,6 @@ }, "node_modules/jest-haste-map": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -9346,8 +8884,6 @@ }, "node_modules/jest-leak-detector": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9360,8 +8896,6 @@ }, "node_modules/jest-matcher-utils": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { @@ -9376,8 +8910,6 @@ }, "node_modules/jest-message-util": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { @@ -9397,8 +8929,6 @@ }, "node_modules/jest-mock": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { @@ -9412,8 +8942,6 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { @@ -9430,8 +8958,6 @@ }, "node_modules/jest-regex-util": { "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { @@ -9440,8 +8966,6 @@ }, "node_modules/jest-resolve": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { @@ -9460,8 +8984,6 @@ }, "node_modules/jest-resolve-dependencies": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { @@ -9474,8 +8996,6 @@ }, "node_modules/jest-runner": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9508,8 +9028,6 @@ }, "node_modules/jest-runtime": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { @@ -9542,8 +9060,6 @@ }, "node_modules/jest-snapshot": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { @@ -9575,8 +9091,6 @@ }, "node_modules/jest-util": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { @@ -9591,23 +9105,8 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/jest-validate": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -9624,8 +9123,6 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -9637,8 +9134,6 @@ }, "node_modules/jest-watcher": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { @@ -9657,8 +9152,6 @@ }, "node_modules/jest-worker": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { @@ -9674,8 +9167,6 @@ }, "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": { @@ -9690,8 +9181,6 @@ }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -9699,8 +9188,6 @@ }, "node_modules/js-git": { "version": "0.7.8", - "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", - "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", "dev": true, "license": "MIT", "dependencies": { @@ -9712,8 +9199,6 @@ }, "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, "license": "MIT" }, @@ -9731,8 +9216,6 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -9744,45 +9227,32 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "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, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC", "optional": true }, "node_modules/json5": { "version": "2.2.3", - "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" @@ -9792,12 +9262,10 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "version": "9.0.2", "license": "MIT", "dependencies": { - "jws": "^4.0.1", + "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -9814,9 +9282,7 @@ } }, "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "version": "1.4.2", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -9825,19 +9291,17 @@ } }, "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^2.0.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -9846,8 +9310,6 @@ }, "node_modules/kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { @@ -9856,14 +9318,10 @@ }, "node_modules/kuler": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", - "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": { @@ -9872,8 +9330,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9885,26 +9341,20 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.36", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.36.tgz", - "integrity": "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ==", + "version": "1.12.25", "license": "MIT" }, "node_modules/lines-and-columns": { "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, "license": "MIT" }, "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "16.2.6", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.2", + "commander": "^14.0.1", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", @@ -9923,9 +9373,7 @@ } }, "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "version": "14.0.2", "dev": true, "license": "MIT", "engines": { @@ -9934,8 +9382,6 @@ }, "node_modules/listr2": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { @@ -9952,8 +9398,6 @@ }, "node_modules/listr2/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -9965,15 +9409,11 @@ }, "node_modules/listr2/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9990,8 +9430,6 @@ }, "node_modules/listr2/node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -10008,8 +9446,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -10036,84 +9472,56 @@ }, "node_modules/lodash.get": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, "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==", "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==", "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "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==", "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==", "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==", "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==", "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "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==", "license": "MIT" }, "node_modules/log-update": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "license": "MIT", "dependencies": { @@ -10131,9 +9539,7 @@ } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "version": "7.1.1", "dev": true, "license": "MIT", "dependencies": { @@ -10148,8 +9554,6 @@ }, "node_modules/log-update/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -10161,15 +9565,11 @@ }, "node_modules/log-update/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10186,8 +9586,6 @@ }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -10204,8 +9602,6 @@ }, "node_modules/logform": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -10227,8 +9623,6 @@ }, "node_modules/lowercase-keys": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "license": "MIT", "engines": { @@ -10240,24 +9634,14 @@ }, "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, "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, - "node_modules/main-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.1.tgz", - "integrity": "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==", - "license": "Apache-2.0 OR MIT" - }, "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, "license": "MIT", "dependencies": { @@ -10272,15 +9656,11 @@ }, "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, "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { @@ -10302,8 +9682,6 @@ }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -10312,8 +9690,6 @@ }, "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": { @@ -10322,8 +9698,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10331,8 +9705,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10340,8 +9712,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -10362,17 +9732,22 @@ "node": ">=10" } }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "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, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -10381,8 +9756,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", "engines": { @@ -10391,8 +9764,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -10403,10 +9774,21 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "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": { @@ -10418,33 +9800,23 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "version": "3.0.1", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -10453,8 +9825,6 @@ }, "node_modules/mimic-function": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { @@ -10466,8 +9836,6 @@ }, "node_modules/mimic-response": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "license": "MIT", "engines": { @@ -10479,8 +9847,6 @@ }, "node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -10495,8 +9861,6 @@ }, "node_modules/minimist": { "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" @@ -10504,8 +9868,6 @@ }, "node_modules/minipass": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -10514,8 +9876,6 @@ }, "node_modules/minipass-collect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "license": "ISC", "dependencies": { @@ -10527,8 +9887,6 @@ }, "node_modules/minipass-fetch": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10545,8 +9903,6 @@ }, "node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "license": "ISC", "dependencies": { @@ -10558,8 +9914,6 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -10571,15 +9925,11 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { @@ -10591,8 +9941,6 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -10604,15 +9952,11 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { @@ -10624,8 +9968,6 @@ }, "node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -10637,15 +9979,11 @@ }, "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/minizlib": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10656,28 +9994,23 @@ } }, "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==", + "version": "1.0.4", + "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, "bin": { "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/module-details-from-path": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "dev": true, "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { "node": "*" @@ -10685,8 +10018,6 @@ }, "node_modules/morgan": { "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", @@ -10701,8 +10032,6 @@ }, "node_modules/morgan/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" @@ -10710,14 +10039,10 @@ }, "node_modules/morgan/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/morgan/node_modules/on-finished": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -10728,8 +10053,6 @@ }, "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==", "license": "MIT" }, "node_modules/multer": { @@ -10780,6 +10103,18 @@ "node": ">= 0.6" } }, + "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==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/multer/node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -10805,19 +10140,15 @@ }, "node_modules/mute-stream": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true, "license": "ISC" }, "node_modules/mylas": { - "version": "2.1.14", - "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", - "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "version": "2.1.13", "dev": true, "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=12.0.0" }, "funding": { "type": "github", @@ -10826,8 +10157,6 @@ }, "node_modules/nano-spawn": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, "license": "MIT", "engines": { @@ -10857,8 +10186,6 @@ }, "node_modules/napi-postinstall": { "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -10882,15 +10209,11 @@ }, "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, "license": "MIT" }, "node_modules/needle": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "dev": true, "license": "MIT", "dependencies": { @@ -10907,8 +10230,6 @@ }, "node_modules/needle/node_modules/debug": { "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10917,8 +10238,6 @@ }, "node_modules/needle/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==", "dev": true, "license": "MIT", "dependencies": { @@ -10930,8 +10249,6 @@ }, "node_modules/negotiator": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10939,15 +10256,11 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/netmask": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, "license": "MIT", "engines": { @@ -10956,8 +10269,6 @@ }, "node_modules/node-addon-api": { "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -10965,8 +10276,6 @@ }, "node_modules/node-config": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/node-config/-/node-config-0.0.2.tgz", - "integrity": "sha512-NZu10oQ7jN6eDkRK22YX8j87mS02CuarKqoWIPcU6MKbuQ5dfLkvjOsWyN4ov+hPkIR7BppEueUg3QtcsRO7MA==", "dev": true, "engines": { "node": ">=0.1.99" @@ -10995,13 +10304,10 @@ "node_modules/node-fetch-native": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==" }, "node_modules/node-gyp": { "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11025,8 +10331,6 @@ }, "node_modules/node-gyp-build": { "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -11036,8 +10340,6 @@ }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "license": "ISC", "engines": { @@ -11046,8 +10348,6 @@ }, "node_modules/node-gyp/node_modules/which": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -11062,31 +10362,24 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", - "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", - "license": "MIT-0", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "version": "3.1.10", "dev": true, "license": "MIT", "dependencies": { @@ -11114,8 +10407,6 @@ }, "node_modules/nodemon/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -11123,10 +10414,46 @@ "concat-map": "0.0.1" } }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" + } + }, "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, "license": "MIT", "engines": { @@ -11135,8 +10462,6 @@ }, "node_modules/nodemon/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -11146,10 +10471,34 @@ "node": "*" } }, + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "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": { @@ -11161,8 +10510,6 @@ }, "node_modules/nopt": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { @@ -11177,8 +10524,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { @@ -11186,9 +10531,7 @@ } }, "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "version": "8.1.0", "dev": true, "license": "MIT", "engines": { @@ -11200,8 +10543,6 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -11212,38 +10553,30 @@ } }, "node_modules/nypm": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", - "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", - "license": "MIT", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", "dependencies": { - "citty": "^0.2.0", + "citty": "^0.1.6", + "consola": "^3.4.2", "pathe": "^2.0.3", - "tinyexec": "^1.0.2" + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": ">=18" + "node": "^14.16.0 || >=16.10.0" } }, - "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", - "license": "MIT" - }, "node_modules/oauth": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", - "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", - "license": "MIT" + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==" }, "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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11251,8 +10584,6 @@ }, "node_modules/object-hash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -11260,8 +10591,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -11273,13 +10602,10 @@ "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "license": "MIT" + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==" }, "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" @@ -11290,8 +10616,6 @@ }, "node_modules/on-headers": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -11299,8 +10623,6 @@ }, "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==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -11308,8 +10630,6 @@ }, "node_modules/one-time": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { "fn.name": "1.x.x" @@ -11317,8 +10637,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -11340,8 +10658,6 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -11358,8 +10674,6 @@ }, "node_modules/p-cancelable": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "license": "MIT", "engines": { @@ -11399,8 +10713,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -11415,8 +10727,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -11430,9 +10740,7 @@ } }, "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "version": "7.0.3", "dev": true, "license": "MIT", "engines": { @@ -11443,9 +10751,9 @@ } }, "node_modules/p-queue": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", - "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", + "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", @@ -11472,8 +10780,6 @@ }, "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": { @@ -11482,8 +10788,6 @@ }, "node_modules/pac-proxy-agent": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, "license": "MIT", "dependencies": { @@ -11502,8 +10806,6 @@ }, "node_modules/pac-resolver": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "license": "MIT", "dependencies": { @@ -11516,22 +10818,16 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "dev": true, "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -11549,8 +10845,6 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -11568,8 +10862,6 @@ }, "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" @@ -11579,7 +10871,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", - "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -11597,7 +10888,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", - "license": "MIT", "dependencies": { "passport-oauth2": "1.x.x" }, @@ -11609,7 +10899,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", - "license": "MIT", "dependencies": { "base64url": "3.x.x", "oauth": "0.10.x", @@ -11635,8 +10924,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -11645,8 +10932,6 @@ }, "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==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11654,8 +10939,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -11664,15 +10947,11 @@ }, "node_modules/path-parse": { "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, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11688,15 +10967,11 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -11705,8 +10980,6 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -11716,8 +10989,7 @@ "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" }, "node_modules/pause": { "version": "0.0.1", @@ -11726,32 +10998,27 @@ }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" }, "node_modules/picocolors": { "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -11759,8 +11026,6 @@ }, "node_modules/pidtree": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, "license": "MIT", "bin": { @@ -11772,8 +11037,6 @@ }, "node_modules/pidusage": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", - "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", "dev": true, "license": "MIT", "dependencies": { @@ -11785,8 +11048,6 @@ }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -11795,8 +11056,6 @@ }, "node_modules/piscina": { "version": "4.9.2", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", - "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -11820,8 +11079,6 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", - "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": { @@ -11833,8 +11090,6 @@ }, "node_modules/pkg-dir/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": { @@ -11847,8 +11102,6 @@ }, "node_modules/pkg-dir/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": { @@ -11860,8 +11113,6 @@ }, "node_modules/pkg-dir/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": { @@ -11876,8 +11127,6 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", - "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": { @@ -11891,7 +11140,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", @@ -11900,8 +11148,6 @@ }, "node_modules/plimit-lit": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", - "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", "dev": true, "license": "MIT", "dependencies": { @@ -11963,8 +11209,6 @@ }, "node_modules/pm2-axon": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", - "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", "dev": true, "license": "MIT", "dependencies": { @@ -11979,8 +11223,6 @@ }, "node_modules/pm2-axon-rpc": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", - "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", "dev": true, "license": "MIT", "dependencies": { @@ -11992,8 +11234,6 @@ }, "node_modules/pm2-deploy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", - "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", "dev": true, "license": "MIT", "dependencies": { @@ -12006,8 +11246,6 @@ }, "node_modules/pm2-multimeter": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", - "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", "dev": true, "license": "MIT/X11", "dependencies": { @@ -12016,8 +11254,6 @@ }, "node_modules/pm2-sysmonit": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", - "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", "dev": true, "license": "Apache", "optional": true, @@ -12031,8 +11267,6 @@ }, "node_modules/pm2-sysmonit/node_modules/pidusage": { "version": "2.0.21", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", - "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", "dev": true, "license": "MIT", "optional": true, @@ -12043,30 +11277,77 @@ "node": ">=8" } }, - "node_modules/pm2/node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/pm2/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/pm2/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/pm2/node_modules/commander": { + "version": "2.15.1", + "dev": true, + "license": "MIT" + }, + "node_modules/pm2/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pm2/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pm2/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "picomatch": "^2.2.1" }, "engines": { - "node": ">=10" + "node": ">=8.10.0" } }, "node_modules/pm2/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -12078,8 +11359,6 @@ }, "node_modules/pm2/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": { @@ -12088,8 +11367,6 @@ }, "node_modules/pm2/node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -12099,8 +11376,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -12108,9 +11383,7 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.6.2", "dev": true, "license": "MIT", "bin": { @@ -12124,9 +11397,7 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { @@ -12138,8 +11409,6 @@ }, "node_modules/pretty-format": { "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { @@ -12153,8 +11422,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -12169,7 +11436,6 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" @@ -12191,8 +11457,6 @@ }, "node_modules/proc-log": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { @@ -12207,8 +11471,6 @@ }, "node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "license": "MIT", "dependencies": { @@ -12219,17 +11481,8 @@ "node": ">=10" } }, - "node_modules/promise-retry/node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" - }, "node_modules/promptly": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", - "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", "dev": true, "license": "MIT", "dependencies": { @@ -12262,8 +11515,6 @@ }, "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", @@ -12275,8 +11526,6 @@ }, "node_modules/proxy-agent": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12295,8 +11544,6 @@ }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -12305,22 +11552,16 @@ }, "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==", "dev": true, "license": "MIT" }, "node_modules/pstree.remy": { "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, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -12329,8 +11570,6 @@ }, "node_modules/pure-rand": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -12344,6 +11583,17 @@ ], "license": "MIT" }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -12361,8 +11611,6 @@ }, "node_modules/queue-lit": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", - "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", "dev": true, "license": "MIT", "engines": { @@ -12371,8 +11619,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -12392,8 +11638,6 @@ }, "node_modules/quick-lru": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "license": "MIT", "engines": { @@ -12407,40 +11651,48 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, "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": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "version": "3.0.1", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" }, "engines": { "node": ">= 0.10" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "license": "MIT", "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -12448,8 +11700,6 @@ }, "node_modules/react-is": { "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" }, @@ -12473,8 +11723,6 @@ }, "node_modules/read": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "license": "ISC", "dependencies": { @@ -12486,8 +11734,6 @@ }, "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", @@ -12499,16 +11745,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/receptacle": { @@ -12522,14 +11768,10 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12537,8 +11779,6 @@ }, "node_modules/require-in-the-middle": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", - "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", "dev": true, "license": "MIT", "dependencies": { @@ -12552,8 +11792,6 @@ }, "node_modules/resolve": { "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12573,15 +11811,11 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true, "license": "MIT" }, "node_modules/resolve-cwd": { "version": "3.0.0", - "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": { @@ -12593,8 +11827,6 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -12603,8 +11835,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -12613,8 +11843,6 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -12623,8 +11851,6 @@ }, "node_modules/responselike": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "license": "MIT", "dependencies": { @@ -12639,8 +11865,6 @@ }, "node_modules/restore-cursor": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { @@ -12656,8 +11880,6 @@ }, "node_modules/restore-cursor/node_modules/onetime": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12672,8 +11894,6 @@ }, "node_modules/restore-cursor/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -12691,8 +11911,6 @@ }, "node_modules/retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { @@ -12701,8 +11919,6 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -12712,15 +11928,11 @@ }, "node_modules/rfdc": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -12735,8 +11947,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -12759,8 +11969,6 @@ }, "node_modules/run-series": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", "dev": true, "funding": [ { @@ -12780,8 +11988,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", @@ -12800,8 +12006,6 @@ }, "node_modules/safe-stable-stringify": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -12809,24 +12013,15 @@ }, "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/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.4.1", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } + "license": "ISC" }, "node_modules/seek-bzip": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", - "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", "dev": true, "license": "MIT", "dependencies": { @@ -12839,8 +12034,6 @@ }, "node_modules/seek-bzip/node_modules/commander": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "license": "MIT", "engines": { @@ -12849,8 +12042,6 @@ }, "node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12861,8 +12052,6 @@ }, "node_modules/semver-regex": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", "engines": { @@ -12874,8 +12063,6 @@ }, "node_modules/semver-truncate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", - "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -12889,35 +12076,27 @@ } }, "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "version": "1.2.0", "license": "MIT", "dependencies": { - "debug": "^4.4.3", + "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "statuses": "^2.0.1" }, "engines": { "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "version": "2.2.0", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -12927,22 +12106,14 @@ }, "engines": { "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "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/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==", "dev": true, "license": "MIT", "dependencies": { @@ -12954,8 +12125,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -12964,15 +12133,11 @@ }, "node_modules/shimmer": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12990,8 +12155,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13006,8 +12169,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13024,8 +12185,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13043,15 +12202,11 @@ }, "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==", "dev": true, "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": { @@ -13063,8 +12218,6 @@ }, "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": { @@ -13073,8 +12226,6 @@ }, "node_modules/slice-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { @@ -13090,8 +12241,6 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -13103,8 +12252,6 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { @@ -13219,8 +12366,6 @@ }, "node_modules/socks": { "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { @@ -13234,8 +12379,6 @@ }, "node_modules/socks-proxy-agent": { "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { @@ -13249,8 +12392,6 @@ }, "node_modules/sort-keys": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, "license": "MIT", "dependencies": { @@ -13262,8 +12403,6 @@ }, "node_modules/sort-keys-length": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, "license": "MIT", "dependencies": { @@ -13273,20 +12412,8 @@ "node": ">=0.10.0" } }, - "node_modules/sort-keys/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map": { "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -13295,8 +12422,6 @@ }, "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": { @@ -13306,8 +12431,6 @@ }, "node_modules/source-map-support/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": { @@ -13316,15 +12439,11 @@ }, "node_modules/sprintf-js": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13336,8 +12455,6 @@ }, "node_modules/stack-trace": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", "engines": { "node": "*" @@ -13345,8 +12462,6 @@ }, "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": { @@ -13358,8 +12473,6 @@ }, "node_modules/stack-utils/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": { @@ -13368,8 +12481,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13394,8 +12505,6 @@ }, "node_modules/streamx": { "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { @@ -13406,8 +12515,6 @@ }, "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" @@ -13415,8 +12522,6 @@ }, "node_modules/string-argv": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", "engines": { @@ -13425,8 +12530,6 @@ }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13439,8 +12542,6 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "engines": { @@ -13449,8 +12550,6 @@ }, "node_modules/string-length/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13462,8 +12561,6 @@ }, "node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -13481,8 +12578,6 @@ "node_modules/string-width-cjs": { "name": "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==", "dev": true, "license": "MIT", "dependencies": { @@ -13496,8 +12591,6 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -13506,15 +12599,11 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -13523,8 +12612,6 @@ }, "node_modules/string-width-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -13536,8 +12623,6 @@ }, "node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -13553,8 +12638,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -13566,8 +12649,6 @@ }, "node_modules/strip-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -13576,8 +12657,6 @@ }, "node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -13586,8 +12665,6 @@ }, "node_modules/strip-dirs": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", - "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13595,20 +12672,8 @@ "is-plain-obj": "^1.1.0" } }, - "node_modules/strip-dirs/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -13617,8 +12682,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -13628,10 +12691,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/strtok3": { "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -13646,9 +12719,7 @@ } }, "node_modules/superagent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", - "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "version": "10.2.3", "dev": true, "license": "MIT", "dependencies": { @@ -13656,45 +12727,30 @@ "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.5", + "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.14.1" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" } }, "node_modules/supertest": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", - "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "version": "7.1.4", "dev": true, "license": "MIT", "dependencies": { - "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.3.0" + "superagent": "^10.2.3" }, "engines": { "node": ">=14.18.0" } }, - "node_modules/supertest/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -13706,8 +12762,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "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": { @@ -13721,7 +12775,6 @@ "version": "2.23.7", "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz", "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^7.4.1", @@ -13734,7 +12787,6 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -13747,7 +12799,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -13759,7 +12810,6 @@ "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", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -13780,7 +12830,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -13791,8 +12840,6 @@ }, "node_modules/swagger-jsdoc": { "version": "6.2.8", - "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", - "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", "license": "MIT", "dependencies": { "commander": "6.2.0", @@ -13811,8 +12858,6 @@ }, "node_modules/swagger-jsdoc/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -13821,8 +12866,6 @@ }, "node_modules/swagger-jsdoc/node_modules/commander": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", "license": "MIT", "engines": { "node": ">= 6" @@ -13830,9 +12873,6 @@ }, "node_modules/swagger-jsdoc/node_modules/glob": { "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -13851,8 +12891,6 @@ }, "node_modules/swagger-jsdoc/node_modules/minimatch": { "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" @@ -13863,8 +12901,6 @@ }, "node_modules/swagger-jsdoc/node_modules/yaml": { "version": "2.0.0-1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", - "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "license": "ISC", "engines": { "node": ">= 6" @@ -13872,8 +12908,6 @@ }, "node_modules/swagger-parser": { "version": "10.0.3", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "10.0.3" @@ -13883,9 +12917,7 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", - "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "version": "5.30.1", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -13907,9 +12939,7 @@ } }, "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "version": "0.11.11", "dev": true, "license": "MIT", "dependencies": { @@ -13923,9 +12953,9 @@ } }, "node_modules/systeminformation": { - "version": "5.30.7", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.7.tgz", - "integrity": "sha512-33B/cftpaWdpvH+Ho9U1b08ss8GQuLxrWHelbJT1yw4M48Taj8W3ezcPuaLoIHZz5V6tVHuQPr5BprEfnBLBMw==", + "version": "5.30.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.5.tgz", + "integrity": "sha512-DpWmpCckhwR3hG+6udb6/aQB7PpiqVnvSljrjbKxNSvTRsGsg7NVE3/vouoYf96xgwMxXFKcS4Ux+cnkFwYM7A==", "dev": true, "license": "MIT", "optional": true, @@ -13951,9 +12981,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -13969,8 +12999,6 @@ }, "node_modules/tar-stream": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13981,8 +13009,6 @@ }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -13991,8 +13017,6 @@ }, "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": { @@ -14006,8 +13030,6 @@ }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -14017,9 +13039,6 @@ }, "node_modules/test-exclude/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", "dev": true, "license": "ISC", "dependencies": { @@ -14039,8 +13058,6 @@ }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -14052,8 +13069,6 @@ }, "node_modules/text-decoder": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14062,14 +13077,10 @@ }, "node_modules/text-hex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, "node_modules/through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true, "license": "MIT" }, @@ -14086,15 +13097,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14108,48 +13116,13 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -14161,21 +13134,17 @@ }, "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/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "version": "6.1.1", "dev": true, "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.2.1", + "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -14189,8 +13158,6 @@ }, "node_modules/touch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, "license": "ISC", "bin": { @@ -14205,17 +13172,13 @@ }, "node_modules/triple-beam": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.1.0", "dev": true, "license": "MIT", "engines": { @@ -14226,9 +13189,7 @@ } }, "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "version": "29.4.5", "dev": true, "license": "MIT", "dependencies": { @@ -14280,8 +13241,6 @@ }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -14293,8 +13252,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, "license": "MIT", "dependencies": { @@ -14337,8 +13294,6 @@ }, "node_modules/tsc-alias": { "version": "1.8.16", - "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", - "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", "dev": true, "license": "MIT", "dependencies": { @@ -14357,20 +13312,80 @@ "node": ">=16.20.2" } }, + "node_modules/tsc-alias/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/tsc-alias/node_modules/commander": { "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } }, + "node_modules/tsc-alias/node_modules/glob-parent": { + "version": "5.1.2", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tsc-alias/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tsc-alias/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "license": "MIT", "dependencies": { @@ -14384,8 +13399,6 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -14394,14 +13407,10 @@ }, "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==", "license": "0BSD" }, "node_modules/tv4": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", "dev": true, "license": [ { @@ -14419,8 +13428,6 @@ }, "node_modules/tx2": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", - "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", "dev": true, "license": "MIT", "optional": true, @@ -14430,8 +13437,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -14443,8 +13448,6 @@ }, "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": { @@ -14453,8 +13456,6 @@ }, "node_modules/type-fest": { "version": "0.21.3", - "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": { @@ -14466,8 +13467,6 @@ }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -14486,14 +13485,10 @@ }, "node_modules/typedi": { "version": "0.10.0", - "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", - "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -14506,8 +13501,6 @@ }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -14522,7 +13515,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" }, @@ -14533,8 +13525,7 @@ "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", - "license": "MIT" + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" }, "node_modules/uint8-varint": { "version": "2.0.4", @@ -14547,9 +13538,9 @@ } }, "node_modules/uint8-varint/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", "license": "Apache-2.0 OR MIT" }, "node_modules/uint8-varint/node_modules/uint8arrays": { @@ -14563,8 +13554,6 @@ }, "node_modules/uint8array-extras": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "dev": true, "license": "MIT", "engines": { @@ -14584,9 +13573,9 @@ } }, "node_modules/uint8arraylist/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", "license": "Apache-2.0 OR MIT" }, "node_modules/uint8arraylist/node_modules/uint8arrays": { @@ -14619,8 +13608,6 @@ }, "node_modules/unbzip2-stream": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "license": "MIT", "dependencies": { @@ -14628,35 +13615,8 @@ "through": "^2.3.8" } }, - "node_modules/unbzip2-stream/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/undefsafe": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, @@ -14674,14 +13634,10 @@ }, "node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unique-filename": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { @@ -14693,8 +13649,6 @@ }, "node_modules/unique-slug": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { @@ -14706,8 +13660,6 @@ }, "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" @@ -14715,8 +13667,6 @@ }, "node_modules/unrs-resolver": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14749,9 +13699,7 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.1.4", "dev": true, "funding": [ { @@ -14781,46 +13729,31 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT" - }, "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/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, "license": "MIT" }, "node_modules/v8-to-istanbul": { "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": { @@ -14833,10 +13766,9 @@ } }, "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", - "license": "MIT", + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", "engines": { "node": ">= 0.10" } @@ -14849,8 +13781,6 @@ }, "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==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -14858,8 +13788,6 @@ }, "node_modules/vizion": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", - "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14874,8 +13802,6 @@ }, "node_modules/vizion/node_modules/async": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "license": "MIT", "dependencies": { @@ -14884,8 +13810,6 @@ }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14910,8 +13834,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -14925,9 +13847,7 @@ } }, "node_modules/winston": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", - "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "version": "3.18.3", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -14948,8 +13868,6 @@ }, "node_modules/winston-daily-rotate-file": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", - "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", "license": "MIT", "dependencies": { "file-stream-rotator": "^0.6.1", @@ -14966,8 +13884,6 @@ }, "node_modules/winston-transport": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { "logform": "^2.7.0", @@ -14980,8 +13896,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -14990,15 +13904,11 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15016,8 +13926,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -15034,8 +13942,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -15044,15 +13950,11 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "engines": { @@ -15061,8 +13963,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -15076,8 +13976,6 @@ }, "node_modules/wrap-ansi-cjs/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==", "dev": true, "license": "MIT", "dependencies": { @@ -15089,8 +13987,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -15102,14 +13998,10 @@ }, "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==", "license": "ISC" }, "node_modules/write-file-atomic": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -15122,8 +14014,6 @@ }, "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -15135,8 +14025,6 @@ }, "node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "license": "MIT", "engines": { @@ -15166,8 +14054,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" @@ -15175,15 +14061,11 @@ }, "node_modules/yallist": { "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.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.1", "dev": true, "license": "ISC", "bin": { @@ -15191,15 +14073,10 @@ }, "engines": { "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -15216,8 +14093,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" @@ -15225,8 +14100,6 @@ }, "node_modules/yargs/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==", "license": "MIT", "engines": { "node": ">=8" @@ -15234,14 +14107,10 @@ }, "node_modules/yargs/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==", "license": "MIT" }, "node_modules/yargs/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==", "license": "MIT", "engines": { "node": ">=8" @@ -15249,8 +14118,6 @@ }, "node_modules/yargs/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==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -15263,8 +14130,6 @@ }, "node_modules/yargs/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==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15275,8 +14140,6 @@ }, "node_modules/yauzl": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", - "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", "dev": true, "license": "MIT", "dependencies": { @@ -15289,8 +14152,6 @@ }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", "engines": { @@ -15299,8 +14160,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -15312,8 +14171,6 @@ }, "node_modules/z-schema": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", - "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", "license": "MIT", "dependencies": { "lodash.get": "^4.4.2", @@ -15332,8 +14189,6 @@ }, "node_modules/z-schema/node_modules/commander": { "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "optional": true, "engines": { diff --git a/package.json b/package.json index e5097b0..404a66a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.3", + "swagger-autogen": "^2.23.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedi": "^0.10.0", @@ -73,7 +74,7 @@ "@types/node": "^24.10.0", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", - "@types/socket.io": "^3.0.2", + "@types/socket.io": "^3.0.1", "@types/supertest": "^6.0.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", @@ -93,7 +94,6 @@ "pm2": "^6.0.13", "prettier": "^3.6.2", "supertest": "^7.1.4", - "swagger-autogen": "^2.23.7", "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "tsc-alias": "^1.8.16", diff --git a/src/app.ts b/src/app.ts index db1ce2c..ead4820 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,41 +21,47 @@ export class App { public app: express.Application; public env: string; public port: string | number; - public httpServer: HttpServer; - private socketService: SocketService; + public httpServer: HttpServer; + private socketService: SocketService; constructor(routes: Routes[]) { this.app = express(); this.env = NODE_ENV || 'development'; this.port = PORT || 3000; this.httpServer = createServer(this.app); - - + this.initializeMiddlewares(); this.initializeRoutes(routes); this.initializeErrorHandling(); this.initializeSwagger(); - - - this.socketService = new SocketService(); + this.socketService = new SocketService(); this.socketService.initialize(this.httpServer); } public listen() { - this.httpServer.listen(this.port, () => { + this.httpServer.listen(this.port); + + this.httpServer.on('listening', () => { logger.info(`=================================`); logger.info(`======= ENV: ${this.env} =======`); - logger.info(`🚀 App listening on the port ${this.port}`); + logger.info(`🚀 App listeningg on the port ${this.port}`); logger.info(`=================================`); }); + + this.httpServer.on('error', (error: any) => { + logger.error('Server failed to start'); + logger.error(error); + process.exit(1); + }); } public getServer() { return this.app; } + private initializeMiddlewares() { this.app.use(morgan(LOG_FORMAT, { stream })); this.app.use(cors({ origin: ORIGIN, credentials: CREDENTIALS })); diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 249aa4c..19c12bb 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -12,7 +12,7 @@ export class AppointmentController { public appointmentService = Container.get(AppointmentService); - public getAvailableDays = catchAsync(async (req: Request, res: Response): Promise => { + public getAvailableDays = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const { doctorId } = req.params; const { clinicId } = req.query; @@ -30,7 +30,7 @@ export class AppointmentController { }); - public getAvailableSlots = catchAsync(async (req: Request, res: Response): Promise => { + public getAvailableSlots = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const { doctorId } = req.params; const { date, clinicId } = req.query; diff --git a/src/controllers/queue.controller.ts b/src/controllers/queue.controller.ts index 3b1de81..6787fff 100644 --- a/src/controllers/queue.controller.ts +++ b/src/controllers/queue.controller.ts @@ -12,8 +12,8 @@ export class QueueController { public queueService = Container.get(QueueService); - public getQueuePosition = catchAsync(async (req: Request, res: Response): Promise => { - const { appointmentId } = req.params; + public getQueuePosition = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const appointmentId = req.params.appointmentId as string; if (!appointmentId) { const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ID_REQUIRED); diff --git a/src/routes/queue.route.ts b/src/routes/queue.route.ts index 20383c6..aa64038 100644 --- a/src/routes/queue.route.ts +++ b/src/routes/queue.route.ts @@ -16,6 +16,45 @@ export class QueueRoute implements Routes { private initializeRoutes() { this.router.get( `${this.path}/position/:appointmentId`, + /* + #swagger.path = '/queue/position/{appointmentId}' + #swagger.method = 'get' + #swagger.tags = ['Queue'] + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID to get its queue position', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Get queue position, number of patients ahead, and estimated waiting time for a specific appointment' + #swagger.responses[200] = { + description: 'Queue position retrieved successfully', + schema: { + data: { + position: 3, + patientsAhead: 2, + estimatedWaitMinutes: 60 + }, + message: 'Queue position retrieved successfully', + } + } + #swagger.responses[400] = { + description: 'Appointment ID is required' + } + #swagger.responses[404] = { + description: 'Appointment not found or doctor not working on this day' + } + #swagger.responses[401] = { + description: 'Unauthorized' + } + */ + AuthMiddleware, this.queueController.getQueuePosition ); diff --git a/src/server.ts b/src/server.ts index 7edad22..8b533b4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,13 +7,14 @@ import { SuperAdminRoute } from './routes/superAdmin.route'; import { DoctorsRoute } from './routes/doctors.route'; import { ClinicRoute } from './routes/clinic.route'; import { AppointmentRoute } from './routes/appointment.route'; +import { QueueRoute } from './routes/queue.route'; ValidateEnv(); const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), - new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute() + new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute(), new QueueRoute(), ]); app.listen(); diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 8ccd670..e81e592 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -9,6 +9,7 @@ import { Service } from 'typedi'; export class QueueService { public async getQueuePosition(appointmentId: string): Promise { + await this.calculateQueuePosition(appointmentId) const appointment = await prisma.appointment.findUnique({ where: { id: appointmentId, @@ -55,7 +56,7 @@ export class QueueService { const schedule = await prisma.doctorSchedule.findFirst({ where: { doctor_id: appointment.doctor_id, - clinic_id: appointment.clinic_id, + clinic_id: appointment?.clinic_id || null, day_of_week: dayOfWeek, is_active: true, deleted_at: null, @@ -65,6 +66,11 @@ export class QueueService { } }); + + if (!schedule){ + const error = createBilingualError(404, ErrorMessages.DOCTOR_NOT_WORKING_ON_DAY); + throw new HttpException(error.status, error.message, error.messageAr); + } const bufferTime = schedule?.buffer_time || 0; const startOfDay = new Date(appointment.scheduled_time); @@ -101,7 +107,7 @@ export class QueueService { throw new HttpException(error.status, error.message, error.messageAr); } - const appointmentsAhead = todayAppointments.slice(0, currentIdx).filter(app => app.status !== 'COMPLETED'); + const appointmentsAhead = todayAppointments.slice(0, currentIdx).filter(app => app.status === 'CONFIRMED'); const patientsAhead = appointmentsAhead.length; const position = currentIdx + 1; diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 67ee521..1c221f0 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -24,8 +24,8 @@ export class SocketService { this.io = new Server(httpServer, { cors: { origin: process.env.ORIGIN, - // credentials: true, - // methods: ['GET', 'POST'], + credentials: true, + }, // polling is just a fallback if websocket fails transports: ['websocket', 'polling'], @@ -148,8 +148,6 @@ export class SocketService { }); } - - private async sendInitialPatientData(patientId: string): Promise { try { const appointments = await this.appointmentService.getPatientAppointments(patientId); @@ -169,7 +167,7 @@ export class SocketService { const schedule = await this.appointmentService.getDoctorSchedule(doctorId); this.emitToUser(doctorId, 'initial_data', { schedule }); } catch (error) { - console.error('Error sending initial doctor data:', error); + console.error('error sending initial doctor data:', error); } } } diff --git a/src/swagger-output.json b/src/swagger-output.json index 5f0c24c..022b1df 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4178,6 +4178,73 @@ } } } + }, + "/queue/position/{appointmentId}": { + "get": { + "tags": [ + "Queue" + ], + "description": "Get queue position, number of patients ahead, and estimated waiting time for a specific appointment", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID to get its queue position" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Queue position retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "position": { + "type": "number", + "example": 3 + }, + "patientsAhead": { + "type": "number", + "example": 2 + }, + "estimatedWaitMinutes": { + "type": "number", + "example": 60 + } + } + }, + "message": { + "type": "string", + "example": "Queue position retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Appointment ID is required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Appointment not found or doctor not working on this day" + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index b980b76..8fcbfe8 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,13 +14,14 @@ const doc = { { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, { name: 'Doctors', description: 'Doctor account endpoints' }, { name: 'Clinics', description: 'Clinic endpoints' }, - { name: 'Appointments', description: 'Appointment endpoints' }, + { name: 'Users', description: 'User account endpoints' }, ], - + }; const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts' , './src/routes/doctors.route.ts' , './src/routes/clinic.route.ts', './src/routes/appointment.route.ts']; + './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' + , './src/routes/user.route.ts', './src/routes/appointment.route.ts', './src/routes/queue.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file From cced383276b2cb0d9e5d7fd9f57a9788dc7d8f6e Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 2 Feb 2026 02:30:19 +0200 Subject: [PATCH 114/210] apply triggers in appointment controller (commented) --- src/controllers/appointment.controller.ts | 15 +++++++++++++- src/services/appointment.service.ts | 24 +++++++++++++++++++++++ src/services/socket.service.ts | 2 +- src/swagger.js | 4 ++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 19c12bb..70e9a25 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -6,11 +6,12 @@ import { AppointmentService } from "@/services/appointment.service" import Container from "typedi"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; - +import { SocketService } from "@/services/socket.service"; export class AppointmentController { public appointmentService = Container.get(AppointmentService); + public socketService = new SocketService(); public getAvailableDays = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const { doctorId } = req.params; @@ -139,7 +140,19 @@ export class AppointmentController { const { appointmentId } = req.params; const { newScheduledTime } = req.body; + // const { doctorId, scheduledTime } = await this.appointmentService.getAppointmentOwners(appointmentId); + await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); + + // await this.socketService.emitQueueUpdatesToPatients(doctorId, new Date(scheduledTime)); + // await this.socketService.emitQueueUpdatesToPatients(doctorId, new Date(newScheduledTime)); + + // this.socketService.emitToUser(doctorId, 'appointment_rescheduled_by_patient', { + // appointmentId, + // patientId, + // oldScheduledTime: scheduledTime, + // newScheduledTime: new Date(newScheduledTime).toISOString(), + // }); const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index cda51b5..3f5635a 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -655,6 +655,30 @@ export class AppointmentService { return schedule; } + public async getAppointmentOwners(appointmentId: string): Promise<{doctorId: string; scheduledTime: Date;}> { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + deleted_at: null, + }, + select: { + doctor_id: true, + patient_id: true, + scheduled_time: true, + }, + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return { + doctorId: appointment.doctor_id, + scheduledTime: appointment.scheduled_time, + }; + } + private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[] { const slots: Omit[] = []; const start = new Date(startTime); diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 1c221f0..594af90 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -170,4 +170,4 @@ export class SocketService { console.error('error sending initial doctor data:', error); } } -} +} \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.js index 8fcbfe8..c3a681a 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -15,6 +15,10 @@ const doc = { { name: 'Doctors', description: 'Doctor account endpoints' }, { name: 'Clinics', description: 'Clinic endpoints' }, { name: 'Users', description: 'User account endpoints' }, + { name: 'Appointments', description: 'Appointment endpoints' }, + { name: 'Queue', description: 'Queue endpoints' }, + + ], }; From f75633ec8e90be3938e382331a999664ef29b186 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 3 Feb 2026 02:18:58 +0200 Subject: [PATCH 115/210] appointment modifications --- package.json | 2 +- src/controllers/appointment.controller.ts | 59 +- src/dtos/appointments.dto.ts | 63 ++- src/interfaces/appointments.interface.ts | 1 + .../migration.sql | 4 + .../migration.sql | 3 + src/prisma/schema.prisma | 7 +- src/routes/appointment.route.ts | 291 +++++----- src/services/appointment.service.ts | 503 ++++++++++-------- src/services/queue.service.ts | 6 +- src/swagger-output.json | 357 ++++++------- src/{swagger.js => swagger.mjs} | 3 +- src/utils/errorMessages.ts | 18 +- src/utils/responseMessages.ts | 4 + 14 files changed, 689 insertions(+), 632 deletions(-) create mode 100644 src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql create mode 100644 src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql rename src/{swagger.js => swagger.mjs} (86%) diff --git a/package.json b/package.json index 404a66a..8ae4b0c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "prisma:migrate": "dotenv -e .env.development.local -- npx prisma migrate dev --schema=src/prisma/schema.prisma", "prisma:generate": "prisma generate", "prisma:studio": "dotenv -e .env.development.local -- prisma studio", - "swagger:generate": "node ./src/swagger.js", + "swagger:generate": "node ./src/swagger.mjs", "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", "deploy:dev": "pm2 start ecosystem.config.js --only dev" }, diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 70e9a25..e94ae55 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -13,7 +13,7 @@ export class AppointmentController { public appointmentService = Container.get(AppointmentService); public socketService = new SocketService(); - public getAvailableDays = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getAvailableDays = catchAsync(async (req: Request, res: Response): Promise => { const { doctorId } = req.params; const { clinicId } = req.query; @@ -31,7 +31,7 @@ export class AppointmentController { }); - public getAvailableSlots = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getAvailableSlots = catchAsync(async (req: Request, res: Response): Promise => { const { doctorId } = req.params; const { date, clinicId } = req.query; @@ -67,7 +67,6 @@ export class AppointmentController { }); - // book a new appointment public bookAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; const { doctorId, clinicId, scheduledTime } = req.body; @@ -153,6 +152,7 @@ export class AppointmentController { // oldScheduledTime: scheduledTime, // newScheduledTime: new Date(newScheduledTime).toISOString(), // }); + // idk const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response @@ -173,79 +173,78 @@ export class AppointmentController { public rescheduleAppointmentByDoctor = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { appointmentId } = req.params; - const { minutes, newScheduledTime } = req.body; + const { minutes } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); throw new HttpException(error.status, error.message, error.messageAr); } - if (minutes && newScheduledTime) { - const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); + if (minutes > 60) { + const error = createBilingualError(400, ErrorMessages.MINUTES_EXCEEDED_LIMIT); throw new HttpException(error.status, error.message, error.messageAr); } - await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined); + await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes); const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response }); }); - public bulkRescheduleByDoctor = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; - const { appointmentIds, minutes, newScheduledTime, keepOriginalSlots } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); throw new HttpException(error.status, error.message, error.messageAr); } - if (minutes && newScheduledTime) { - const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); - throw new HttpException(error.status, error.message, error.messageAr); - } - - await this.appointmentService.bulkRescheduleByDoctor(doctorId, appointmentIds, minutes, newScheduledTime ? new Date(newScheduledTime) : undefined, keepOriginalSlots); - const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_RESCHEDULED_SUCCESSFULLY); + const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); res.status(200).json({ + data: schedule, ...response }); }); - public rescheduleDayAppointments = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getCurrentDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; - const { currentDate, minutes, newDate, keepOriginalSlots } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); throw new HttpException(error.status, error.message, error.messageAr); } - if (minutes && newDate) { - const error = createBilingualError(400, ErrorMessages.EITHER_MINUTES_OR_NEW_TIME); - throw new HttpException(error.status, error.message, error.messageAr); - } - - await this.appointmentService.rescheduleDayAppointments(doctorId, new Date(currentDate), minutes, newDate ? new Date(newDate) : undefined, keepOriginalSlots); - const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_RESCHEDULED_SUCCESSFULLY); + const schedule = await this.appointmentService.getCurrentDoctorSchedule(doctorId); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); res.status(200).json({ + data: schedule, ...response }); }); - public getDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public enterDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; + const { + clinicId, + workingDay, + startTime, + endTime, + slotDuration, + bufferTime, + isOnline, + } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); throw new HttpException(error.status, error.message, error.messageAr); } - const schedule = await this.appointmentService.getDoctorSchedule(doctorId); - const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); - res.status(200).json({ - data: schedule, + await this.appointmentService.enterDoctorSchedule(doctorId, clinicId || null, workingDay, startTime, endTime, slotDuration, bufferTime, isOnline); + + const response = createMultiLangMessage(SuccessResponseMessages.SCHEDULE_CREATED_SUCCESSFULLY); + res.status(201).json({ ...response }); }); diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index 9f9877a..31c2b2c 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsNotEmpty, IsDateString, IsOptional, IsUUID, IsNumber, ValidateIf, IsArray } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsDateString, IsOptional, IsUUID, IsNumber, ValidateIf, IsString, Min, IsInt, Max } from 'class-validator'; export class BookAppointmentDto { @@ -48,43 +48,42 @@ export class RescheduleAppointmentDto { export class RescheduleAppointmentByDoctorDto { @IsNumber() - @IsOptional() - minutes?: number; - - @IsDateString() - @IsOptional() - newScheduledTime?: string; + minutes: number; } -export class BulkRescheduleDto { - @IsArray() - @IsUUID('4', { each: true }) - @IsNotEmpty() - appointmentIds: string[]; - @IsNumber() - @IsOptional() - minutes?: number; +export class EnterDoctorScheduleDto { + @IsUUID('4') + @IsNotEmpty() + doctorId: string; - @IsDateString() - @IsOptional() - newScheduledTime?: string; + @IsUUID('4') + @IsOptional() + clinicId?: string | null; - @IsBoolean() - @IsOptional() - keepOriginalSlots?: boolean; -} + @IsInt() + @Min(0) + @Max(6) + @IsNotEmpty() + workingDay: number; -export class RescheduleDayDto { - @IsDateString() - @IsNotEmpty() - currentDate: string; + @IsString() + @IsNotEmpty() + startTime: string; - @IsDateString() - @IsNotEmpty() - newDate: string; + @IsString() + @IsNotEmpty() + endTime: string; - @IsBoolean() - @IsOptional() - keepOriginalSlots?: boolean; + @IsInt() + @IsNotEmpty() + slotDuration: number; + + @IsInt() + @IsOptional() + bufferTime?: number = 0; + + @IsBoolean() + @IsNotEmpty() + isOnline: boolean; } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 4b2f29a..78408bf 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -79,6 +79,7 @@ export interface TimeSlot { start: string; end: string; available: boolean; + online: boolean; } diff --git a/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql b/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql new file mode 100644 index 0000000..72da5d5 --- /dev/null +++ b/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "DoctorSchedules" ADD COLUMN "break_end" TEXT, +ADD COLUMN "break_start" TEXT, +ADD COLUMN "is_online" BOOLEAN NOT NULL DEFAULT true; diff --git a/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql b/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql new file mode 100644 index 0000000..99642c7 --- /dev/null +++ b/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "DoctorSchedules" ALTER COLUMN "start_time" SET DATA TYPE TEXT, +ALTER COLUMN "end_time" SET DATA TYPE TEXT; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index cfddd8d..f6be6ed 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -245,11 +245,14 @@ model DoctorSchedule { doctor_id String clinic_id String? day_of_week DayOfWeek - start_time DateTime @db.Time(0) - end_time DateTime @db.Time(0) + start_time String + end_time String slot_duration Int buffer_time Int @default(0) + is_online Boolean @default(true) is_active Boolean @default(true) + break_start String? + break_end String? created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 2c0ddc8..bb174e5 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -5,7 +5,7 @@ import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, BulkRescheduleDto, RescheduleDayDto } from "@/dtos/appointments.dto"; +import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto } from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -19,7 +19,6 @@ export class AppointmentRoute implements Routes { } private initializeRoutes() { - // get online doctors this.router.get( `${this.path}/online-doctors`, /* @@ -137,7 +136,7 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Get all available days for a doctor that have at least one available slot' + #swagger.description = 'Get available days for booking with a specific doctor (up to 30 days ahead)' #swagger.parameters['doctorId'] = { in: 'path', description: 'Doctor ID', @@ -158,26 +157,16 @@ export class AppointmentRoute implements Routes { date: '2026-02-03', dayOfWeek: 'MONDAY', displayDate: 'Monday, February 3, 2026' - }, - { - date: '2026-02-05', - dayOfWeek: 'WEDNESDAY', - displayDate: 'Wednesday, February 5, 2026' - }, - { - date: '2026-02-10', - dayOfWeek: 'MONDAY', - displayDate: 'Monday, February 10, 2026' } ], - message: 'Available days retrieved successfully', + message: 'Available days retrieved successfully' } } #swagger.responses[400] = { - description: 'Bad request - missing required parameters' + description: 'Bad request - missing doctor ID or invalid parameters' } - #swagger.responses[404] = { - description: 'Doctor not found or not available' + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' } */ AuthMiddleware, @@ -197,7 +186,7 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Get all available time slots for a doctor on a specific date' + #swagger.description = 'Get available time slots for a specific doctor on a given date' #swagger.parameters['doctorId'] = { in: 'path', description: 'Doctor ID', @@ -208,8 +197,7 @@ export class AppointmentRoute implements Routes { in: 'query', description: 'Date in YYYY-MM-DD format', required: true, - type: 'string', - example: '2026-02-03' + type: 'string' } #swagger.parameters['clinicId'] = { in: 'query', @@ -223,26 +211,25 @@ export class AppointmentRoute implements Routes { data: [ { start: '09:00', - end: '09:20' + end: '09:20', + available: true, + online: true }, { start: '09:30', - end: '09:50' - }, - { - start: '10:00', - end: '10:20' - }, - { - start: '10:30', - end: '10:50' + end: '09:50', + available: false, + online: true } ], - message: 'Available slots retrieved successfully', + message: 'Available slots retrieved successfully' } } #swagger.responses[400] = { - description: 'Bad request - missing required parameters or invalid date' + description: 'Bad request - missing date, invalid format, or past date' + } + #swagger.responses[401] = { + description: 'Unauthorized - user not authenticated' } */ AuthMiddleware, @@ -554,11 +541,11 @@ export class AppointmentRoute implements Routes { #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication (doctor)', + description: 'Bearer token for authentication (must be a doctor)', required: true, type: 'string' } - #swagger.description = 'Reschedule an appointment by the doctor. Doctor can either shift the appointment by a number of minutes or set a new scheduled time (but not both)' + #swagger.description = 'Reschedule an appointment by adding minutes (delay) as a doctor' #swagger.parameters['appointmentId'] = { in: 'path', description: 'Appointment ID to reschedule', @@ -567,11 +554,10 @@ export class AppointmentRoute implements Routes { } #swagger.parameters['body'] = { in: 'body', - description: 'Reschedule parameters (provide either minutes OR newScheduledTime)', + description: 'Minutes to add (max 60)', required: true, schema: { - minutes: 15, - newScheduledTime: '2026-02-05T11:30:00.000Z' + minutes: 30 } } #swagger.responses[200] = { @@ -581,16 +567,13 @@ export class AppointmentRoute implements Routes { } } #swagger.responses[400] = { - description: 'Bad request - invalid reschedule parameters', - schema: { - message: 'Error message describing the issue' - } + description: 'Bad request - missing minutes, exceeds limit, or invalid parameters' } #swagger.responses[401] = { description: 'Unauthorized - doctor not authenticated' } #swagger.responses[403] = { - description: 'Forbidden - appointment does not belong to the authenticated doctor' + description: 'Forbidden - appointment does not belong to the doctor' } #swagger.responses[404] = { description: 'Appointment not found' @@ -601,126 +584,6 @@ export class AppointmentRoute implements Routes { this.appointmentController.rescheduleAppointmentByDoctor ); - this.router.patch( - `${this.path}/doctor/bulk-reschedule`, - /* - #swagger.path = '/appointments/doctor/bulk-reschedule' - #swagger.method = 'patch' - #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication (doctor)', - required: true, - type: 'string' - } - #swagger.description = 'Bulk reschedule multiple appointments by the authenticated doctor. \ - Rules: \ - (1) You must provide EITHER "minutes" OR "newScheduledTime" (not both). \ - (2) When using "minutes", all appointments are shifted by the same number of minutes. \ - (3) When using "newScheduledTime": \ - - If "keepOriginalSlots" is true, appointments keep their original time-of-day but move to the new date. \ - - If "keepOriginalSlots" is false, appointments are reallocated sequentially based on the doctor schedule.' - - #swagger.parameters['body'] = { - in: 'body', - description: 'Bulk reschedule parameters', - required: true, - schema: { - appointmentIds: [ - 'appointment-uuid-1', - 'appointment-uuid-2', - 'appointment-uuid-3' - ], - minutes: 15, - newScheduledTime: '2026-02-10T09:00:00.000Z', - keepOriginalSlots: true - } - } - #swagger.responses[200] = { - description: 'Appointments rescheduled successfully', - schema: { - message: 'Appointments rescheduled successfully' - } - } - #swagger.responses[400] = { - description: 'Bad request - invalid or conflicting reschedule parameters', - schema: { - message: 'Error message describing the issue' - } - } - #swagger.responses[401] = { - description: 'Unauthorized - doctor not authenticated' - } - #swagger.responses[403] = { - description: 'Forbidden - one or more appointments do not belong to the authenticated doctor' - } - #swagger.responses[404] = { - description: 'One or more appointments not found' - } - */ - AuthMiddleware, - ValidationMiddleware(BulkRescheduleDto), - this.appointmentController.bulkRescheduleByDoctor - ); - - this.router.patch( - `${this.path}/doctor/reschedule-day`, - /* - #swagger.path = '/appointments/doctor/reschedule-day' - #swagger.method = 'patch' - #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication (doctor)', - required: true, - type: 'string' - } - #swagger.description = 'Reschedule all appointments on a specific day by the authenticated doctor. \ - Rules: \ - (1) You must provide EITHER "minutes" OR "newDate" (not both). \ - (2) When using "minutes", all appointments on the specified day are shifted by the same number of minutes. \ - (3) When using "newDate": \ - - If "keepOriginalSlots" is true, appointments keep their original time-of-day but move to the new date. \ - - If "keepOriginalSlots" is false, appointments are reallocated sequentially based on the doctor schedule.' - - #swagger.parameters['body'] = { - in: 'body', - description: 'Reschedule day parameters', - required: true, - schema: { - currentDate: '2026-02-10T09:00:00.000Z', - minutes: 15, - newDate: '2026-02-13T09:00:00.000Z', - keepOriginalSlots: true - } - } - #swagger.responses[200] = { - description: 'Appointments rescheduled successfully', - schema: { - message: 'Appointments rescheduled successfully' - } - } - #swagger.responses[400] = { - description: 'Bad request - invalid or conflicting reschedule parameters', - schema: { - message: 'Error message describing the issue' - } - } - #swagger.responses[401] = { - description: 'Unauthorized - doctor not authenticated' - } - #swagger.responses[403] = { - description: 'Forbidden - one or more appointments do not belong to the authenticated doctor' - } - #swagger.responses[404] = { - description: 'No appointments found on the specified day' - } - */ - AuthMiddleware, - ValidationMiddleware(RescheduleDayDto), - this.appointmentController.rescheduleDayAppointments - ) - this.router.get( `${this.path}/doctor/schedule`, /* @@ -796,5 +659,109 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.getDoctorSchedule ); + + this.router.post( + `${this.path}/doctor/schedule`, + /* + #swagger.path = '/appointments/doctor/schedule' + #swagger.method = 'post' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Enter or update a doctor's schedule for a specific day' + #swagger.parameters['body'] = { + in: 'body', + description: 'Schedule details', + required: true, + schema: { + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', + workingDay: 1, + startTime: '09:00', + endTime: '17:00', + slotDuration: 30, + bufferTime: 5, + isOnline: true + } + } + #swagger.responses[201] = { + description: 'Schedule created successfully', + schema: { + message: 'Schedule created successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid parameters or doctor ID missing' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + */ + AuthMiddleware, + ValidationMiddleware(EnterDoctorScheduleDto), + this.appointmentController.enterDoctorSchedule + ); + + this.router.get( + `${this.path}/doctor/current-schedule`, + /* + #swagger.path = '/appointments/doctor/current-schedule' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Get all confirmed appointments for doctor today' + #swagger.responses[200] = { + description: 'Today\'s appointments retrieved successfully', + schema: { + data: [ + { + id: 'appointment-uuid', + status: 'CONFIRMED', + slot_duration: 30, + patient_name: 'John Doe', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' + }, + { + id: 'appointment-uuid-2', + status: 'CONFIRMED', + slot_duration: 20, + patient_name: 'Jane Smith', + appointment_date: '2026-02-03', + start_time: '10:15', + end_time: '10:35', + clinic_name: null, + clinic_address: null + } + ], + message: { + en: "Doctor's schedule retrieved successfully", + ar: "تم استرجاع جدول الطبيب بنجاح" + } + } + } + #swagger.responses[401] = { + description: 'Unauthorized - invalid or missing token' + } + #swagger.responses[400] = { + description: 'Bad request (should rarely happen here)' + } + */ + AuthMiddleware, + this.appointmentController.getCurrentDoctorSchedule + ); + } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 3f5635a..992c936 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -16,26 +16,24 @@ export class AppointmentService { public async getAvailableDays(doctorId: string, clinicId: string | null): Promise { const daysAhead = 30 const availableDays: AvailableDay[] = []; - const isOnline = await this.doctorIsOnline(doctorId); - - if (!isOnline && !clinicId) { - const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); - throw new HttpException(error.status, error.message, error.messageAr); - } const schedules = await prisma.doctorSchedule.findMany({ where: { doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, - is_active: true, + clinic_id: clinicId, deleted_at: null }, select: { day_of_week: true, start_time: true, end_time: true, + is_online: true, slot_duration: true, - buffer_time: true + buffer_time: true, + is_active: true, + break_start: true, + break_end: true, + } }); @@ -56,14 +54,14 @@ export class AppointmentService { }); const today = new Date(); - today.setHours(0, 0, 0, 0); + today.setUTCHours(0, 0, 0, 0); for (let i = 1; i <= daysAhead; i++) { // create a copy from today --> if we used today directly it will be modified to today + 1 --> tomorrow date const currentDate = new Date(today); - currentDate.setDate(today.getDate() + i); // current day now is = today + 1 + currentDate.setUTCDate(today.getUTCDate() + i); // current day now is = today + 1 - const dayOfWeek = this.getDayOfWeek(currentDate.getDay()); + const dayOfWeek = this.getDayOfWeek(currentDate.getUTCDay()); const schedule = scheduleMap.get(dayOfWeek); // skip if doctor doesnt work on this day @@ -71,7 +69,32 @@ export class AppointmentService { continue; } - const hasAvailableSlots = true; + + if (!schedule.is_online && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!schedule.is_active) { + if (schedule.break_start && schedule.break_end) { + const breakStart = new Date(schedule.break_start); + breakStart.setUTCHours(0, 0, 0, 0); + + const breakEnd = new Date(schedule.break_end); + breakEnd.setUTCHours(23, 59, 59, 999); + + const currentDateOnly = new Date(currentDate); + currentDateOnly.setUTCHours(0, 0, 0, 0); + + if (currentDateOnly >= breakStart && currentDateOnly <= breakEnd) { + continue; + } + } + } + + const availableSlots = await this.getAvailableSlots(doctorId, clinicId, this.formatDate(currentDate)); + const hasAvailableSlots = availableSlots.length > 0; + if (hasAvailableSlots) { availableDays.push({ @@ -84,32 +107,26 @@ export class AppointmentService { return availableDays; } - public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise[]> { - const requestedDate = new Date(date); - const dayOfWeek = this.getDayOfWeek(requestedDate.getDay()); - const isOnline = await this.doctorIsOnline(doctorId); - if (!isOnline && !clinicId) { - const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); - throw new HttpException(error.status, error.message, error.messageAr); - } + public async getAvailableSlots(doctorId: string, clinicId: string | null, date: string): Promise { + const requestedDate = new Date(date); + const dayOfWeek = this.getDayOfWeek(requestedDate.getUTCDay()); const today = new Date(); - today.setHours(0, 0, 0, 0); + today.setUTCHours(0, 0, 0, 0); + const requestedDateOnly = new Date(requestedDate); - requestedDateOnly.setHours(0, 0, 0, 0); + requestedDateOnly.setUTCHours(0, 0, 0, 0); if (requestedDateOnly < today) { const error = createBilingualError(400, ErrorMessages.APPOINTMENT_IN_PAST); throw new HttpException(error.status, error.message, error.messageAr); } - const schedule = await prisma.doctorSchedule.findFirst({ + const schedules = await prisma.doctorSchedule.findMany({ where: { day_of_week: dayOfWeek, doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, - is_active: true, deleted_at: null, }, select: { @@ -117,25 +134,27 @@ export class AppointmentService { end_time: true, slot_duration: true, buffer_time: true, + is_online: true, + }, + + orderBy: { + start_time: 'asc' } }); - if (!schedule) { + + if (schedules.length === 0) { return []; } - - const allSlots = this.generateTimeSlots(schedule.start_time, schedule.end_time, schedule.slot_duration, schedule.buffer_time); - const startOfDay = new Date(requestedDate); - startOfDay.setHours(0, 0, 0, 0); + startOfDay.setUTCHours(0, 0, 0, 0); const endOfDay = new Date(requestedDate); - endOfDay.setHours(23, 59, 59, 999); + endOfDay.setUTCHours(23, 59, 59, 999); const existingAppointments = await prisma.appointment.findMany({ where: { doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, scheduled_time: { gte: startOfDay, lte: endOfDay, @@ -148,53 +167,77 @@ export class AppointmentService { select: { scheduled_time: true, end_time: true, + is_online: true, } }); - const availableSlots = allSlots.filter(slot => { - const slotStart = this.parseTimeToDate(requestedDate, slot.start); - const slotEnd = this.parseTimeToDate(requestedDate, slot.end); + const allSlots: TimeSlot[] = []; + + + for (const schedule of schedules) { + const isOnline = schedule.is_online; + + if (!schedule.is_online && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const thisScheduleSlots = this.generateTimeSlots(schedule.start_time, schedule.end_time, schedule.slot_duration, schedule.buffer_time, isOnline); + + const finalSlots = thisScheduleSlots.map(slot => { + const slotStart = this.parseTimeToDate(requestedDate, slot.start); + const slotEnd = this.parseTimeToDate(requestedDate, slot.end); + + const isBooked = existingAppointments.some(appt => { + const apptStart = new Date(appt.scheduled_time); + const apptEnd = new Date(appt.end_time); + return this.doesSlotOverlap(slotStart, slotEnd, apptStart, apptEnd); + }); - const isBooked = existingAppointments.some(appointment => { - const appointmentStart = new Date(appointment.scheduled_time); - const appointmentEnd = new Date(appointment.end_time); + const now = new Date(); + const isInPast = slotEnd <= now; - return this.doesSlotOverlap(slotStart, slotEnd, appointmentStart, appointmentEnd); + return { + start: slot.start, + end: slot.end, + available: !isBooked && !isInPast, + online: isOnline + } satisfies TimeSlot; }); - // check if the slot is in the past (now the time is x, we cant book a slot before x) - const now = new Date(); - const isInPast = slotEnd <= now; + allSlots.push(...finalSlots) + } - return !isBooked && !isInPast; - }); + allSlots.sort((a, b) => a.start.localeCompare(b.start)); + return allSlots; - return availableSlots; } public async bookAppointment(patientId: string, doctorId: string, clinicId: string | null, scheduledTime: Date): Promise { - const isOnline = await this.doctorIsOnline(doctorId); - if (!isOnline && !clinicId) { - const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); - throw new HttpException(error.status, error.message, error.messageAr); - } - const schedule = await prisma.doctorSchedule.findFirst({ where: { doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, + clinic_id: clinicId, is_active: true, deleted_at: null, - day_of_week: this.getDayOfWeek(scheduledTime.getDay()), + day_of_week: this.getDayOfWeek(scheduledTime.getUTCDay()), }, select: { slot_duration: true, + is_online: true, } }); + const isOnline = schedule.is_online; + + if (!isOnline && !clinicId) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + const endTime = new Date(scheduledTime.getTime() + schedule.slot_duration * 60000); - const appointment = await prisma.appointment.create({ + await prisma.appointment.create({ data: { patient_id: patientId, doctor_id: doctorId, @@ -231,7 +274,7 @@ export class AppointmentService { address: true, } } - }, + }, orderBy: { scheduled_time: 'asc', } @@ -296,9 +339,9 @@ export class AppointmentService { public async getTodayAppointment(patientId: string): Promise { const today = new Date(); - today.setHours(0, 0, 0, 0); + today.setUTCHours(0, 0, 0, 0); const endOfToday = new Date(); - endOfToday.setHours(23, 59, 59, 999); + endOfToday.setUTCHours(23, 59, 59, 999); const appointment = await prisma.appointment.findFirst({ where: { @@ -390,154 +433,90 @@ export class AppointmentService { // penalty to be added later } - public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes?: number, newScheduledTime?: Date, rescheduleDay?: boolean): Promise { + public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes: number): Promise { const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); - - let updatedScheduledTime: Date; - let updatedEndTime: Date; - - if (minutes) { - updatedScheduledTime = new Date(appointment.scheduled_time.getTime() + minutes * 60000); - updatedEndTime = new Date(appointment.end_time.getTime() + minutes * 60000); - } else { - updatedScheduledTime = newScheduledTime; - updatedEndTime = new Date(newScheduledTime.getTime() + appointment.slot_duration * 60000); - } - - if (rescheduleDay !== true) { - await this.validateDoctorAvailability(doctorId, appointment.clinic_id, updatedScheduledTime, updatedEndTime, appointmentId); - } - - await prisma.appointment.update({ + const appointments = await prisma.appointment.findMany({ where: { - id: appointmentId, + doctor_id: doctorId, + status: "CONFIRMED", + deleted_at: null, + scheduled_time: { + gte: appointment.scheduled_time + } }, - data: { - scheduled_time: updatedScheduledTime, - end_time: updatedEndTime, - modified_at: new Date(), + select: { + id: true } - }); - } + }) - public async bulkRescheduleByDoctor(doctorId: string, appointmentIds: string[], minutes?: number, newBaseDate?: Date, keepOriginalSlots?: boolean): Promise { - if (minutes) { - for (const appointmentId of appointmentIds) { - await this.getAndValidateAppointment(appointmentId, doctorId); - await this.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes, undefined); - } + for (const { id: appointmentId } of appointments) { + await this.rescheduleSingleAppointment(doctorId, appointmentId, minutes); } + } - if (newBaseDate) { - if (keepOriginalSlots) { - for (const appointmentId of appointmentIds) { - await this.getAndValidateAppointment(appointmentId, doctorId); - - const appointment = await prisma.appointment.findUnique({ - where: { id: appointmentId }, - select: { scheduled_time: true }, - }); - const originalTime = new Date(appointment.scheduled_time); - - const newScheduledTime = new Date(newBaseDate); - newScheduledTime.setHours(originalTime.getHours(), originalTime.getMinutes(), 0, 0); + public async enterDoctorSchedule(doctorId: string, clinicId: string | null, workingDay: number, startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Promise { + if (clinicId) { + const clinic = await prisma.clinic.findUnique({ + where: { id: clinicId }, + select: { id: true } + }); - await this.rescheduleAppointmentByDoctor(doctorId, appointmentId, null, newScheduledTime); - } + if (!clinic) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); } - else { - const appointments = await prisma.appointment.findMany({ - where: { - id: { in: appointmentIds }, - doctor_id: doctorId, - status: { in: ['CONFIRMED'] }, - deleted_at: null, - }, - select: { - id: true, - scheduled_time: true, - slot_duration: true, - clinic_id: true, - }, - orderBy: { - scheduled_time: 'asc', - } - }); - const dayOfWeek = this.getDayOfWeek(newBaseDate.getDay()); - const isOnline = await this.doctorIsOnline(doctorId); - const clinicId = appointments[0]?.clinic_id || null; - - const schedule = await prisma.doctorSchedule.findFirst({ - where: { - day_of_week: dayOfWeek, - doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, - is_active: true, - deleted_at: null, - }, - select: { - slot_duration: true, - buffer_time: true, + const clinicDoctor = await prisma.clinicDoctor.findUnique({ + where: { + clinic_id_doctor_id: { + clinic_id: clinicId, + doctor_id: doctorId } - }); - - let currentSlotStart = new Date(newBaseDate); - - for (let i = 0; i < appointments.length; i++) { - const appointment = appointments[i]; - await this.getAndValidateAppointment(appointment.id, doctorId); - const newScheduledTime = new Date(currentSlotStart); - - await this.rescheduleAppointmentByDoctor(doctorId, appointment.id, null, newScheduledTime); - - // move to next slot - currentSlotStart = new Date(currentSlotStart.getTime() + (schedule.slot_duration + schedule.buffer_time) * 60000); } + }); + + if (!clinicDoctor) { + const error = createBilingualError(403, ErrorMessages.DOCTOR_NOT_ASSOCIATED_WITH_CLINIC); + throw new HttpException(error.status, error.message, error.messageAr); } } - } - - public async rescheduleDayAppointments(doctorId: string, currentDate: Date, minutes?: number, newDate?: Date, keepOriginalSlots?: boolean): Promise { - const rescheduleDay: boolean = true; + const startMinutes = this.timeStringToMinutes(startTime); + const endMinutes = this.timeStringToMinutes(endTime); + const dayOfWeek = this.getDayOfWeek(workingDay); - const startOfDay = new Date(currentDate); - startOfDay.setHours(0, 0, 0, 0); - - const endOfDay = new Date(currentDate); - endOfDay.setHours(23, 59, 59, 999); + if (startMinutes >= endMinutes) { + const error = createBilingualError(400, ErrorMessages.END_TIME_BEFORE_START_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } - const appointments = await prisma.appointment.findMany({ + const existingSchedule = await prisma.doctorSchedule.findFirst({ where: { doctor_id: doctorId, - scheduled_time: { - gte: startOfDay, - lte: endOfDay, - }, - status: { in: ['CONFIRMED'] }, - deleted_at: null, - }, - select: { - id: true, - scheduled_time: true, - }, - orderBy: { - scheduled_time: 'asc', + clinic_id: clinicId, + day_of_week: dayOfWeek, + deleted_at: null } }); - if (minutes) { - for (const appointment of appointments) { - await this.getAndValidateAppointment(appointment.id, doctorId); - await this.rescheduleAppointmentByDoctor(doctorId, appointment.id, minutes, undefined, rescheduleDay); - } - } - - else if (newDate) { - await this.bulkRescheduleByDoctor(doctorId, appointments.map(app => app.id), null, newDate, keepOriginalSlots); + if (existingSchedule) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ALREADY_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); } + await prisma.doctorSchedule.create({ + data: { + doctor_id: doctorId, + clinic_id: clinicId, + day_of_week: dayOfWeek, + start_time: startTime, + end_time: endTime, + slot_duration: slotDuration, + buffer_time: bufferTime, + is_online: isOnline, + is_active: true + } + }); } public async cancelAppointment(userId: string, appointmentId: string): Promise { @@ -592,7 +571,7 @@ export class AppointmentService { scheduled_time: { gte: new Date(), }, - status: { in: ['CONFIRMED', 'COMPLETED'] }, + status: 'CONFIRMED', deleted_at: null, }, orderBy: { @@ -644,7 +623,7 @@ export class AppointmentService { const schedule: DoctorScheduleDay[] = []; groupedByDate.forEach((appointments, dateKey) => { - const date = new Date(dateKey); + const date = new Date(dateKey + 'T00:00:00.000Z'); schedule.push({ date: dateKey, displayDate: this.formatDisplayDate(date), @@ -655,16 +634,75 @@ export class AppointmentService { return schedule; } - public async getAppointmentOwners(appointmentId: string): Promise<{doctorId: string; scheduledTime: Date;}> { + public async getCurrentDoctorSchedule(doctorId: string): Promise { + const startOfDay = new Date(); + startOfDay.setUTCHours(0, 0, 0, 0); + + const endOfDay = new Date(); + endOfDay.setUTCHours(23, 59, 59, 999); + + const appointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay + }, + status: 'CONFIRMED', + deleted_at: null, + }, + orderBy: { + scheduled_time: 'asc', + }, + select: { + id: true, + scheduled_time: true, + end_time: true, + slot_duration: true, + status: true, + clinic_id: true, + patient: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + } + }); + + if (appointments.length === 0) { + return null; + } + + return appointments.map(app => ({ + id: app.id, + status: app.status, + slot_duration: app.slot_duration, + patient_name: app.patient.name, + appointment_date: this.formatDate(app.scheduled_time), + start_time: this.formatTime(app.scheduled_time), + end_time: this.formatTime(app.end_time), + clinic_name: app.clinic ? app.clinic.name : null, + clinic_address: app.clinic ? app.clinic.address : null, + })); + + } + + public async getAppointmentOwners(appointmentId: string): Promise<{ doctorId: string; scheduledTime: Date; }> { const appointment = await prisma.appointment.findUnique({ where: { - id: appointmentId, - deleted_at: null, + id: appointmentId, + deleted_at: null, }, select: { - doctor_id: true, - patient_id: true, - scheduled_time: true, + doctor_id: true, + patient_id: true, + scheduled_time: true, }, }); @@ -677,25 +715,27 @@ export class AppointmentService { doctorId: appointment.doctor_id, scheduledTime: appointment.scheduled_time, }; - } + } - private generateTimeSlots(startTime: Date, endTime: Date, slotDuration: number, bufferTime: number): Omit[] { + private generateTimeSlots(startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Omit[] { const slots: Omit[] = []; - const start = new Date(startTime); - const end = new Date(endTime); - let currentTime = new Date(start); + const startMinutes = this.timeStringToMinutes(startTime); + const endMinutes = this.timeStringToMinutes(endTime); + + let currentMinutes = startMinutes; - while (currentTime < end) { - const slotEnd = new Date(currentTime.getTime() + slotDuration * 60000); - if (slotEnd <= end) { + while (currentMinutes < endMinutes) { + const slotEndMinutes = currentMinutes + slotDuration; + if (slotEndMinutes <= endMinutes) { slots.push({ - start: this.formatTime(currentTime), - end: this.formatTime(slotEnd), + start: this.minutesToTimeString(currentMinutes), + end: this.minutesToTimeString(slotEndMinutes), + online: isOnline }); } // move to next slot (slot duration + buffer time) - currentTime = new Date(currentTime.getTime() + (slotDuration + bufferTime) * 60000); + currentMinutes += (slotDuration + bufferTime); } return slots; @@ -717,9 +757,9 @@ export class AppointmentService { // format date as YYYY-MM-DD private formatDate(date: Date): string { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } @@ -730,6 +770,7 @@ export class AppointmentService { year: 'numeric', month: 'long', day: 'numeric', + timeZone: 'UTC' }; // later --> for arabic ar-EG return date.toLocaleDateString('en-EG', options); @@ -737,19 +778,33 @@ export class AppointmentService { // extract time from date / ex: 1970-01-01T09:00:00.000Z --> 09:00 private formatTime(date: Date): string { - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); + const hours = String(date.getUTCHours()).padStart(2, '0'); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); return `${hours}:${minutes}`; } // date: 2026-01-27, time string: 10:30 --> 2026-01-27 10:30:00 private parseTimeToDate(date: Date, timeStr: string): Date { const [hours, minutes] = timeStr.split(':').map(Number); - const result = new Date(date); - result.setHours(hours, minutes, 0, 0); + const result = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0)); + result.setUTCHours(hours, minutes, 0, 0); return result; } + // ex: "10:30" --> 630 + private timeStringToMinutes(timeStr: string): number { + console.log(`string ${timeStr}`) + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + } + + // "HH:MM" format + private minutesToTimeString(minutes: number): string { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`; + } + private doesSlotOverlap(slotStart: Date, slotEnd: Date, appointmentStart: Date, appointmentEnd: Date): boolean { return (slotStart < appointmentEnd && slotEnd > appointmentStart); } @@ -766,6 +821,28 @@ export class AppointmentService { return availability_type === 'ONLINE' || availability_type === 'BOTH'; } + + private async rescheduleSingleAppointment(doctorId: string, appointmentId: string, minutes: number): Promise { + const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); + + let updatedScheduledTime: Date; + let updatedEndTime: Date; + + updatedScheduledTime = new Date(appointment.scheduled_time.getTime() + minutes * 60000); + updatedEndTime = new Date(appointment.end_time.getTime() + minutes * 60000); + + await prisma.appointment.update({ + where: { + id: appointmentId, + }, + data: { + scheduled_time: updatedScheduledTime, + end_time: updatedEndTime, + modified_at: new Date(), + } + }); + } + private async getAndValidateAppointment(appointmentId: string, doctorId: string) { const appointment = await prisma.appointment.findUnique({ where: { id: appointmentId }, @@ -803,7 +880,7 @@ export class AppointmentService { private async validateDoctorAvailability(doctorId: string, clinicId: string | null, newScheduledTime: Date, newEndTime: Date, excludeAppointmentId?: string): Promise { // check if doctor works on this day - const dayOfWeek = this.getDayOfWeek(newScheduledTime.getDay()); + const dayOfWeek = this.getDayOfWeek(newScheduledTime.getUTCDay()); const isOnline = await this.doctorIsOnline(doctorId); const schedule = await prisma.doctorSchedule.findFirst({ @@ -826,8 +903,8 @@ export class AppointmentService { } // check if the new time within schedule or not - const scheduleStart = this.parseTimeToDate(newScheduledTime, this.formatTime(schedule.start_time)); - const scheduleEnd = this.parseTimeToDate(newScheduledTime, this.formatTime(schedule.end_time)); + const scheduleStart = this.parseTimeToDate(newScheduledTime, schedule.start_time); + const scheduleEnd = this.parseTimeToDate(newScheduledTime, schedule.end_time); if (newScheduledTime < scheduleStart || newEndTime > scheduleEnd) { const error = createBilingualError(400, ErrorMessages.TIME_OUTSIDE_SCHEDULE); @@ -836,10 +913,10 @@ export class AppointmentService { // check for any conflicts with existing appointments (appointments on the same calendar day) const startOfDay = new Date(newScheduledTime); - startOfDay.setHours(0, 0, 0, 0); + startOfDay.setUTCHours(0, 0, 0, 0); const endOfDay = new Date(newScheduledTime); - endOfDay.setHours(23, 59, 59, 999); + endOfDay.setUTCHours(23, 59, 59, 999); const whereClause: any = { doctor_id: doctorId, diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index e81e592..009eeb5 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -51,7 +51,7 @@ export class QueueService { throw new HttpException(error.status, error.message, error.messageAr); } - const dayOfWeek = this.getDayOfWeek(appointment.scheduled_time.getDay()); + const dayOfWeek = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); const schedule = await prisma.doctorSchedule.findFirst({ where: { @@ -74,10 +74,10 @@ export class QueueService { const bufferTime = schedule?.buffer_time || 0; const startOfDay = new Date(appointment.scheduled_time); - startOfDay.setHours(0, 0, 0, 0); + startOfDay.setUTCHours(0, 0, 0, 0); const endOfDay = new Date(appointment.scheduled_time); - endOfDay.setHours(23, 59, 59, 999); + endOfDay.setUTCHours(23, 59, 59, 999); const todayAppointments = await prisma.appointment.findMany({ where: { diff --git a/src/swagger-output.json b/src/swagger-output.json index 022b1df..066ef66 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -35,6 +35,10 @@ { "name": "Appointments", "description": "Appointment endpoints" + }, + { + "name": "Queue", + "description": "Queue endpoints" } ], "schemes": [ @@ -3189,7 +3193,7 @@ "tags": [ "Appointments" ], - "description": "Get all available days for a doctor that have at least one available slot", + "description": "Get available days for booking with a specific doctor (up to 30 days ahead)", "parameters": [ { "name": "doctorId", @@ -3226,7 +3230,7 @@ "properties": { "date": { "type": "string", - "example": "2026-02-10" + "example": "2026-02-03" }, "dayOfWeek": { "type": "string", @@ -3234,7 +3238,7 @@ }, "displayDate": { "type": "string", - "example": "Monday, February 10, 2026" + "example": "Monday, February 3, 2026" } } } @@ -3250,10 +3254,10 @@ } }, "400": { - "description": "Bad request - missing required parameters" + "description": "Bad request - missing doctor ID or invalid parameters" }, - "404": { - "description": "Doctor not found or not available" + "401": { + "description": "Unauthorized - user not authenticated" } } } @@ -3263,7 +3267,7 @@ "tags": [ "Appointments" ], - "description": "Get all available time slots for a doctor on a specific date", + "description": "Get available time slots for a specific doctor on a given date", "parameters": [ { "name": "doctorId", @@ -3284,8 +3288,7 @@ "in": "query", "description": "Date in YYYY-MM-DD format", "required": true, - "type": "string", - "example": "2026-02-03" + "type": "string" }, { "name": "clinicId", @@ -3308,11 +3311,19 @@ "properties": { "start": { "type": "string", - "example": "10:30" + "example": "09:30" }, "end": { "type": "string", - "example": "10:50" + "example": "09:50" + }, + "available": { + "type": "boolean", + "example": false + }, + "online": { + "type": "boolean", + "example": true } } } @@ -3328,7 +3339,10 @@ } }, "400": { - "description": "Bad request - missing required parameters or invalid date" + "description": "Bad request - missing date, invalid format, or past date" + }, + "401": { + "description": "Unauthorized - user not authenticated" } } } @@ -3827,7 +3841,7 @@ "tags": [ "Appointments" ], - "description": "Reschedule an appointment by the doctor. Doctor can either shift the appointment by a number of minutes or set a new scheduled time (but not both)", + "description": "Reschedule an appointment by adding minutes (delay) as a doctor", "parameters": [ { "name": "appointmentId", @@ -3839,25 +3853,21 @@ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication (doctor)", + "description": "Bearer token for authentication (must be a doctor)", "required": true, "type": "string" }, { "name": "body", "in": "body", - "description": "Reschedule parameters (provide either minutes OR newScheduledTime)", + "description": "Minutes to add (max 60)", "required": true, "schema": { "type": "object", "properties": { "minutes": { "type": "number", - "example": 15 - }, - "newScheduledTime": { - "type": "string", - "example": "2026-02-05T11:30:00.000Z" + "example": 30 } } } @@ -3880,25 +3890,13 @@ } }, "400": { - "description": "Bad request - invalid reschedule parameters", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Error message describing the issue" - } - }, - "xml": { - "name": "main" - } - } + "description": "Bad request - missing minutes, exceeds limit, or invalid parameters" }, "401": { "description": "Unauthorized - doctor not authenticated" }, "403": { - "description": "Forbidden - appointment does not belong to the authenticated doctor" + "description": "Forbidden - appointment does not belong to the doctor" }, "404": { "description": "Appointment not found" @@ -3906,64 +3904,86 @@ } } }, - "/appointments/doctor/bulk-reschedule": { - "patch": { + "/appointments/doctor/schedule": { + "get": { "tags": [ "Appointments" ], - "description": "Bulk reschedule multiple appointments by the authenticated doctor. \\ Rules: \\ (1) You must provide EITHER \"minutes\" OR \"newScheduledTime\" (not both). \\ (2) When using \"minutes\", all appointments are shifted by the same number of minutes. \\ (3) When using \"newScheduledTime\": \\ - If \"keepOriginalSlots\" is true, appointments keep their original time-of-day but move to the new date. \\ - If \"keepOriginalSlots\" is false, appointments are reallocated sequentially based on the doctor schedule.", + "description": "Get the doctor", "parameters": [ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication (doctor)", + "description": "Bearer token for authentication (must be a doctor)", "required": true, "type": "string" - }, - { - "name": "body", - "in": "body", - "description": "Bulk reschedule parameters", - "required": true, - "schema": { - "type": "object", - "properties": { - "appointmentIds": { - "type": "array", - "example": [ - "appointment-uuid-1", - "appointment-uuid-2", - "appointment-uuid-3" - ], - "items": { - "type": "string" - } - }, - "minutes": { - "type": "number", - "example": 15 - }, - "newScheduledTime": { - "type": "string", - "example": "2026-02-10T09:00:00.000Z" - }, - "keepOriginalSlots": { - "type": "boolean", - "example": true - } - } - } } ], "responses": { "200": { - "description": "Appointments rescheduled successfully", + "description": "Doctor schedule retrieved successfully", "schema": { "type": "object", "properties": { - "message": { - "type": "string", - "example": "Appointments rescheduled successfully" + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "example": "2026-02-05" + }, + "displayDate": { + "type": "string", + "example": "Wednesday, February 5, 2026" + }, + "appointments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid-3" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "slot_duration": { + "type": "number", + "example": 45 + }, + "patient_name": { + "type": "string", + "example": "Bob Johnson" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-05" + }, + "start_time": { + "type": "string", + "example": "14:00" + }, + "end_time": { + "type": "string", + "example": "14:45" + }, + "clinic_name": { + "type": "string", + "example": "Downtown Health Center" + }, + "clinic_address": { + "type": "string", + "example": "456 Oak Avenue" + } + } + } + } + } + } } }, "xml": { @@ -3972,67 +3992,63 @@ } }, "400": { - "description": "Bad request - invalid or conflicting reschedule parameters", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Error message describing the issue" - } - }, - "xml": { - "name": "main" - } - } + "description": "Bad request - doctor ID missing or invalid" }, "401": { "description": "Unauthorized - doctor not authenticated" - }, - "403": { - "description": "Forbidden - one or more appointments do not belong to the authenticated doctor" - }, - "404": { - "description": "One or more appointments not found" } } - } - }, - "/appointments/doctor/reschedule-day": { - "patch": { + }, + "post": { "tags": [ "Appointments" ], - "description": "Reschedule all appointments on a specific day by the authenticated doctor. \\ Rules: \\ (1) You must provide EITHER \"minutes\" OR \"newDate\" (not both). \\ (2) When using \"minutes\", all appointments on the specified day are shifted by the same number of minutes. \\ (3) When using \"newDate\": \\ - If \"keepOriginalSlots\" is true, appointments keep their original time-of-day but move to the new date. \\ - If \"keepOriginalSlots\" is false, appointments are reallocated sequentially based on the doctor schedule.", + "description": "Enter or update a doctor", "parameters": [ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication (doctor)", + "description": "Bearer token for authentication (must be a doctor)", "required": true, "type": "string" }, { "name": "body", "in": "body", - "description": "Reschedule day parameters", + "description": "Schedule details", "required": true, "schema": { "type": "object", "properties": { - "currentDate": { + "doctorId": { "type": "string", - "example": "2026-02-10T09:00:00.000Z" + "example": "doctor-uuid" }, - "minutes": { + "clinicId": { + "type": "string", + "example": "clinic-uuid" + }, + "workingDay": { "type": "number", - "example": 15 + "example": 1 + }, + "startTime": { + "type": "string", + "example": "09:00" }, - "newDate": { + "endTime": { "type": "string", - "example": "2026-02-13T09:00:00.000Z" + "example": "17:00" + }, + "slotDuration": { + "type": "number", + "example": 30 + }, + "bufferTime": { + "type": "number", + "example": 5 }, - "keepOriginalSlots": { + "isOnline": { "type": "boolean", "example": true } @@ -4041,14 +4057,14 @@ } ], "responses": { - "200": { - "description": "Appointments rescheduled successfully", + "201": { + "description": "Schedule created successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Appointments rescheduled successfully" + "example": "Schedule created successfully" } }, "xml": { @@ -4057,38 +4073,20 @@ } }, "400": { - "description": "Bad request - invalid or conflicting reschedule parameters", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Error message describing the issue" - } - }, - "xml": { - "name": "main" - } - } + "description": "Bad request - invalid parameters or doctor ID missing" }, "401": { "description": "Unauthorized - doctor not authenticated" - }, - "403": { - "description": "Forbidden - one or more appointments do not belong to the authenticated doctor" - }, - "404": { - "description": "No appointments found on the specified day" } } } }, - "/appointments/doctor/schedule": { + "/appointments/doctor/current-schedule": { "get": { "tags": [ "Appointments" ], - "description": "Get the doctor", + "description": "Get all confirmed appointments for doctor today", "parameters": [ { "name": "Authorization", @@ -4100,7 +4098,7 @@ ], "responses": { "200": { - "description": "Doctor schedule retrieved successfully", + "description": "Today's appointments retrieved successfully", "schema": { "type": "object", "properties": { @@ -4109,58 +4107,49 @@ "items": { "type": "object", "properties": { - "date": { + "id": { "type": "string", - "example": "2026-02-05" + "example": "appointment-uuid-2" }, - "displayDate": { + "status": { "type": "string", - "example": "Wednesday, February 5, 2026" + "example": "CONFIRMED" }, - "appointments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "appointment-uuid-3" - }, - "status": { - "type": "string", - "example": "CONFIRMED" - }, - "slot_duration": { - "type": "number", - "example": 45 - }, - "patient_name": { - "type": "string", - "example": "Bob Johnson" - }, - "appointment_date": { - "type": "string", - "example": "2026-02-05" - }, - "start_time": { - "type": "string", - "example": "14:00" - }, - "end_time": { - "type": "string", - "example": "14:45" - }, - "clinic_name": { - "type": "string", - "example": "Downtown Health Center" - }, - "clinic_address": { - "type": "string", - "example": "456 Oak Avenue" - } - } - } - } + "slot_duration": { + "type": "number", + "example": 20 + }, + "patient_name": { + "type": "string", + "example": "Jane Smith" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-03" + }, + "start_time": { + "type": "string", + "example": "10:15" + }, + "end_time": { + "type": "string", + "example": "10:35" + }, + "clinic_name": {}, + "clinic_address": {} + } + } + }, + "message": { + "type": "object", + "properties": { + "en": { + "type": "string", + "example": "Doctor's schedule retrieved successfully" + }, + "ar": { + "type": "string", + "example": "تم استرجاع جدول الطبيب بنجاح" } } } @@ -4171,10 +4160,10 @@ } }, "400": { - "description": "Bad request - doctor ID missing or invalid" + "description": "Bad request (should rarely happen here)" }, "401": { - "description": "Unauthorized - doctor not authenticated" + "description": "Unauthorized - invalid or missing token" } } } diff --git a/src/swagger.js b/src/swagger.mjs similarity index 86% rename from src/swagger.js rename to src/swagger.mjs index c3a681a..78f633a 100644 --- a/src/swagger.js +++ b/src/swagger.mjs @@ -14,7 +14,6 @@ const doc = { { name: 'MedicalRecords', description: 'Hyperledger Fabric medical record endpoints' }, { name: 'Doctors', description: 'Doctor account endpoints' }, { name: 'Clinics', description: 'Clinic endpoints' }, - { name: 'Users', description: 'User account endpoints' }, { name: 'Appointments', description: 'Appointment endpoints' }, { name: 'Queue', description: 'Queue endpoints' }, @@ -26,6 +25,6 @@ const doc = { const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' - , './src/routes/user.route.ts', './src/routes/appointment.route.ts', './src/routes/queue.route.ts']; + , './src/routes/appointment.route.ts', './src/routes/queue.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index a97f06c..d0e55b1 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -124,6 +124,18 @@ export const ErrorMessages = { en: "Requested time is outside doctor's working hours", ar: "الوقت المطلوب خارج ساعات عمل الطبيب" }, + DOCTOR_NOT_ASSOCIATED_WITH_CLINIC: { + en: 'Doctor is not associated with this clinic', + ar: 'الطبيب غير مرتبط بهذه العيادة' + }, + END_TIME_BEFORE_START_TIME: { + en: 'End time must be after start time', + ar: 'وقت الانتهاء يجب أن يكون بعد وقت البداية' + }, + SCHEDULE_ALREADY_EXISTS: { + en: 'Schedule already exists for this day and clinic', + ar: 'الجدول موجود بالفعل لهذا اليوم والعيادة' + }, // Clinic errors CLINIC_NOT_FOUND: { @@ -179,9 +191,9 @@ export const ErrorMessages = { en: "This time slot is not available", ar: "هذا الوقت غير متاح" }, - EITHER_MINUTES_OR_NEW_TIME: { - en: "Provide either shift minutes or new scheduled time, not both", - ar: "يرجى تقديم إما دقائق التغيير أو وقت موعد جديد، وليس كلاهما" + MINUTES_EXCEEDED_LIMIT: { + en: "The maximum allowed delay must not exceed 60 minutes.", + ar: "يجب ألا يتجاوز الحد الأقصى للتأجيل المسموح به 60 دقيقة." }, // Generic errors SOMETHING_WENT_WRONG: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 7244aee..a3e462a 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -104,6 +104,10 @@ export const SuccessResponseMessages = { message_en: "Google user data retrieved successfully.", message_ar: "تم استرجاع بيانات مستخدم جوجل بنجاح.", }, + SCHEDULE_CREATED_SUCCESSFULLY: { + message_en: 'Schedule created successfully', + message_ar: 'تم إنشاء الجدول بنجاح' + }, // Success messages for Super Admin ADMIN_ADDED_SUCCESSFULLY: { From a952dca799115222be1d7e920cb3785aaa33827d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 3 Feb 2026 14:44:23 +0200 Subject: [PATCH 116/210] swagger modifications --- package.json | 2 +- src/routes/appointment.route.ts | 1 - src/services/appointment.service.ts | 8 ++++++-- src/{swagger.mjs => swagger.js} | 0 src/utils/errorMessages.ts | 6 +++++- 5 files changed, 12 insertions(+), 5 deletions(-) rename src/{swagger.mjs => swagger.js} (100%) diff --git a/package.json b/package.json index 8ae4b0c..404a66a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "prisma:migrate": "dotenv -e .env.development.local -- npx prisma migrate dev --schema=src/prisma/schema.prisma", "prisma:generate": "prisma generate", "prisma:studio": "dotenv -e .env.development.local -- prisma studio", - "swagger:generate": "node ./src/swagger.mjs", + "swagger:generate": "node ./src/swagger.js", "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", "deploy:dev": "pm2 start ecosystem.config.js --only dev" }, diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index bb174e5..bd471ce 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -762,6 +762,5 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.getCurrentDoctorSchedule ); - } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 992c936..c2b6cf1 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -20,7 +20,6 @@ export class AppointmentService { const schedules = await prisma.doctorSchedule.findMany({ where: { doctor_id: doctorId, - clinic_id: clinicId, deleted_at: null }, select: { @@ -228,6 +227,11 @@ export class AppointmentService { } }); + if (!schedule) { + const error = createBilingualError(400, ErrorMessages.DAY_OUTSIDE_SCHEDULE); + throw new HttpException(error.status, error.message, error.messageAr); + } + const isOnline = schedule.is_online; if (!isOnline && !clinicId) { @@ -237,6 +241,7 @@ export class AppointmentService { const endTime = new Date(scheduledTime.getTime() + schedule.slot_duration * 60000); + await prisma.appointment.create({ data: { patient_id: patientId, @@ -793,7 +798,6 @@ export class AppointmentService { // ex: "10:30" --> 630 private timeStringToMinutes(timeStr: string): number { - console.log(`string ${timeStr}`) const [hours, minutes] = timeStr.split(':').map(Number); return hours * 60 + minutes; } diff --git a/src/swagger.mjs b/src/swagger.js similarity index 100% rename from src/swagger.mjs rename to src/swagger.js diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index d0e55b1..c44ad5d 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -124,6 +124,10 @@ export const ErrorMessages = { en: "Requested time is outside doctor's working hours", ar: "الوقت المطلوب خارج ساعات عمل الطبيب" }, + DAY_OUTSIDE_SCHEDULE: { + en: "Requested date is outside doctor's working days", + ar: "الموعد المطلوب خارج أيام عمل الطبيب" + }, DOCTOR_NOT_ASSOCIATED_WITH_CLINIC: { en: 'Doctor is not associated with this clinic', ar: 'الطبيب غير مرتبط بهذه العيادة' @@ -136,7 +140,7 @@ export const ErrorMessages = { en: 'Schedule already exists for this day and clinic', ar: 'الجدول موجود بالفعل لهذا اليوم والعيادة' }, - + // Clinic errors CLINIC_NOT_FOUND: { en: 'Clinic not found', From 88427ce1befaa3ac16cd1758268919cbdea50af3 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 3 Feb 2026 17:09:03 +0200 Subject: [PATCH 117/210] fix: solve migration issues --- backup_neon_2026-02-03.sql | 0 .../migration.sql | 4 - .../migration.sql | 3 - .../migration.sql | 74 +++++++++++++++++++ src/prisma/schema.prisma | 14 ++-- 5 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 backup_neon_2026-02-03.sql delete mode 100644 src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql delete mode 100644 src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql create mode 100644 src/prisma/migrations/20260203141848_fix_off_time_and_timezone/migration.sql diff --git a/backup_neon_2026-02-03.sql b/backup_neon_2026-02-03.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql b/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql deleted file mode 100644 index 72da5d5..0000000 --- a/src/prisma/migrations/20260202151555_add_off_time_parameters/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- AlterTable -ALTER TABLE "DoctorSchedules" ADD COLUMN "break_end" TEXT, -ADD COLUMN "break_start" TEXT, -ADD COLUMN "is_online" BOOLEAN NOT NULL DEFAULT true; diff --git a/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql b/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql deleted file mode 100644 index 99642c7..0000000 --- a/src/prisma/migrations/20260202193020_solve_timezone_problem/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "DoctorSchedules" ALTER COLUMN "start_time" SET DATA TYPE TEXT, -ALTER COLUMN "end_time" SET DATA TYPE TEXT; diff --git a/src/prisma/migrations/20260203141848_fix_off_time_and_timezone/migration.sql b/src/prisma/migrations/20260203141848_fix_off_time_and_timezone/migration.sql new file mode 100644 index 0000000..845dda1 --- /dev/null +++ b/src/prisma/migrations/20260203141848_fix_off_time_and_timezone/migration.sql @@ -0,0 +1,74 @@ +/* + Warnings: + + - Added the required column `end_time` to the `Appointments` table without a default value. This is not possible if the table is not empty. + - Added the required column `slot_duration` to the `Appointments` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "AvailabilityType" AS ENUM ('UNSET', 'ONLINE', 'OFFLINE', 'BOTH'); + +-- CreateEnum +CREATE TYPE "DayOfWeek" AS ENUM ('SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'); + +-- CreateEnum +CREATE TYPE "AppointmentStatus" AS ENUM ('CONFIRMED', 'COMPLETED', 'CANCELLED', 'NO_SHOW'); + +-- AlterTable +ALTER TABLE "Appointments" ADD COLUMN "cancelled_by" TEXT, +ADD COLUMN "clinic_id" TEXT, +ADD COLUMN "end_time" TIMESTAMP(3) NOT NULL, +ADD COLUMN "slot_duration" INTEGER NOT NULL, +ADD COLUMN "status" "AppointmentStatus" NOT NULL DEFAULT 'CONFIRMED'; + +-- AlterTable +ALTER TABLE "ClinicDoctor" ADD COLUMN "is_accepting" BOOLEAN NOT NULL DEFAULT true; + +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "availability_type" "AvailabilityType" NOT NULL DEFAULT 'UNSET', +ADD COLUMN "present" BOOLEAN NOT NULL DEFAULT true; + +-- CreateTable +CREATE TABLE "DoctorSchedules" ( + "id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "clinic_id" TEXT, + "day_of_week" "DayOfWeek" NOT NULL, + "start_time" TEXT NOT NULL, + "end_time" TEXT NOT NULL, + "slot_duration" INTEGER NOT NULL, + "buffer_time" INTEGER NOT NULL DEFAULT 0, + "is_online" BOOLEAN NOT NULL DEFAULT true, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "break_start" TEXT, + "break_end" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "DoctorSchedules_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_doctor_id_idx" ON "DoctorSchedules"("doctor_id"); + +-- CreateIndex +CREATE INDEX "DoctorSchedules_clinic_id_idx" ON "DoctorSchedules"("clinic_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "DoctorSchedules_doctor_id_clinic_id_day_of_week_key" ON "DoctorSchedules"("doctor_id", "clinic_id", "day_of_week"); + +-- CreateIndex +CREATE INDEX "Appointments_clinic_id_idx" ON "Appointments"("clinic_id"); + +-- CreateIndex +CREATE INDEX "Appointments_status_idx" ON "Appointments"("status"); + +-- AddForeignKey +ALTER TABLE "Appointments" ADD CONSTRAINT "Appointments_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DoctorSchedules" ADD CONSTRAINT "DoctorSchedules_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index f92f421..614d6af 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -63,9 +63,9 @@ model Doctor { fellowshipCertificatePublicId String? @db.VarChar(500) unionSpecializationCertificateUrl String? @db.VarChar(500) unionSpecializationCertificatePublicId String? @db.VarChar(500) - availability_type AvailabilityType @default(UNSET) - present Boolean @default(true) - clinic_doctors ClinicDoctor[] + availability_type AvailabilityType @default(UNSET) + present Boolean @default(true) + clinic_doctors ClinicDoctor[] user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) doctorSchedules DoctorSchedule[] @@ -90,8 +90,8 @@ model Appointment { scheduled_time DateTime is_online Boolean @default(false) is_completed Boolean @default(false) - position Int @default(0) - patients_ahead Int @default(0) + position Int @default(0) + patients_ahead Int @default(0) estimated_time Float? created_at DateTime @default(now()) modified_at DateTime @updatedAt @@ -263,8 +263,8 @@ model DoctorSchedule { buffer_time Int @default(0) is_online Boolean @default(true) is_active Boolean @default(true) - break_start String? - break_end String? + break_start String? + break_end String? created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? From 464375309f448ab9ebdabacd2f7aa986b75139d1 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 4 Feb 2026 22:02:15 +0200 Subject: [PATCH 118/210] add missing response msg --- src/controllers/doctor.controller.ts | 2 -- src/utils/responseMessages.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 4c3149c..9cb7ebd 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -51,6 +51,4 @@ export class DoctorController { const doctors = await this.doctorService.getOnlineDoctors(); res.status(200).json({ data: doctors, message: 'Online doctors retrieved successfully' }); } - - } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 9c12c4f..49b9195 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -169,6 +169,14 @@ export const SuccessResponseMessages = { message_en: "Queue position retrieved successfully.", message_ar: "تم استرجاع موقعك في قائمة الانتظار بنجاح.", }, + SCHEDULE_CREATED_SUCCESSFULLY: { + message_en: 'Schedule created successfully', + message_ar: 'تم إنشاء الجدول بنجاح' + }, + DOCTOR_SCHEDULE_RETRIEVED: { + message_en: 'Doctor schedule retrieved successfully', + message_ar: 'تم استرجاع جدول الطبيب بنجاح' + } } From da5e858c00fefcf3a558e253caa3db6dd90d0984 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 4 Feb 2026 22:17:21 +0200 Subject: [PATCH 119/210] fix: swagger comment for doctor schedule --- package.json | 2 +- src/routes/appointment.route.ts | 3 +- src/swagger-output.json | 264 ++++++++++++++++---------------- src/{swagger.js => swagger.mjs} | 0 4 files changed, 132 insertions(+), 137 deletions(-) rename src/{swagger.js => swagger.mjs} (100%) diff --git a/package.json b/package.json index 404a66a..8ae4b0c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "prisma:migrate": "dotenv -e .env.development.local -- npx prisma migrate dev --schema=src/prisma/schema.prisma", "prisma:generate": "prisma generate", "prisma:studio": "dotenv -e .env.development.local -- prisma studio", - "swagger:generate": "node ./src/swagger.js", + "swagger:generate": "node ./src/swagger.mjs", "deploy:prod": "npm run build && pm2 start ecosystem.config.js --only prod", "deploy:dev": "pm2 start ecosystem.config.js --only dev" }, diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index bd471ce..0e06dd3 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -678,8 +678,7 @@ export class AppointmentRoute implements Routes { description: 'Schedule details', required: true, schema: { - doctorId: 'doctor-uuid', - clinicId: 'clinic-uuid', + clinicId: 'clinic-uuid (optional)', workingDay: 1, startTime: '09:00', endTime: '17:00', diff --git a/src/swagger-output.json b/src/swagger-output.json index 1d5c678..7b326c1 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3173,135 +3173,6 @@ } } }, - "/users/profile-picture": { - "patch": { - "tags": [ - "Users" - ], - "description": "", - "consumes": [ - "multipart/form-data" - ], - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - }, - { - "name": "profilePicture", - "in": "formData", - "type": "file", - "required": true, - "description": "Profile picture file" - } - ], - "responses": { - "200": { - "description": "Profile picture updated successfully", - "schema": { - "type": "object", - "properties": { - "messageEn": { - "type": "string", - "example": "Profile picture updated successfully" - }, - "messageAr": { - "type": "string", - "example": "تم تحديث صورة الملف الشخصي بنجاح" - } - }, - "xml": { - "name": "main" - } - } - } - } - }, - "get": { - "tags": [ - "Users" - ], - "description": "", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Get profile picture successful", - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "url": { - "type": "string", - "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" - } - } - }, - "messageEn": { - "type": "string", - "example": "Profile picture retrieved successfully" - }, - "messageAr": { - "type": "string", - "example": "تم استرجاع صورة الملف الشخصي بنجاح" - } - }, - "xml": { - "name": "main" - } - } - } - } - }, - "delete": { - "tags": [ - "Users" - ], - "description": "", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Profile picture deleted successfully", - "schema": { - "type": "object", - "properties": { - "messageEn": { - "type": "string", - "example": "Profile picture deleted successfully" - }, - "messageAr": { - "type": "string", - "example": "تم حذف صورة الملف الشخصي بنجاح" - } - }, - "xml": { - "name": "main" - } - } - } - } - } - }, "/appointments/online-doctors": { "get": { "tags": [ @@ -4315,13 +4186,9 @@ "schema": { "type": "object", "properties": { - "doctorId": { - "type": "string", - "example": "doctor-uuid" - }, "clinicId": { "type": "string", - "example": "clinic-uuid" + "example": "clinic-uuid (optional)" }, "workingDay": { "type": "number", @@ -4529,6 +4396,135 @@ } } } + }, + "/users/profile-picture": { + "patch": { + "tags": [ + "Users" + ], + "description": "", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "profilePicture", + "in": "formData", + "type": "file", + "required": true, + "description": "Profile picture file" + } + ], + "responses": { + "200": { + "description": "Profile picture updated successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Profile picture updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث صورة الملف الشخصي بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get profile picture successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://res.cloudinary.com/your-cloud-name/image/upload/v1696543210/doctors/profile_pictures/doctor_1_profile_picture_1696543210.jpg" + } + } + }, + "messageEn": { + "type": "string", + "example": "Profile picture retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع صورة الملف الشخصي بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "delete": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Profile picture deleted successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Profile picture deleted successfully" + }, + "messageAr": { + "type": "string", + "example": "تم حذف صورة الملف الشخصي بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.js b/src/swagger.mjs similarity index 100% rename from src/swagger.js rename to src/swagger.mjs From 16dfcdab51e965b662121407205076c26dc53634 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 5 Feb 2026 01:18:09 +0200 Subject: [PATCH 120/210] fix: validation appointments dto --- src/dtos/appointments.dto.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index 31c2b2c..dff2ed6 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -53,11 +53,6 @@ export class RescheduleAppointmentByDoctorDto { export class EnterDoctorScheduleDto { - @IsUUID('4') - @IsNotEmpty() - doctorId: string; - - @IsUUID('4') @IsOptional() clinicId?: string | null; From ea08087b11f2c11d0a1fa78d59fa0b7caa935930 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 5 Feb 2026 02:55:23 +0200 Subject: [PATCH 121/210] add get/edit doctor schedule --- src/controllers/appointment.controller.ts | 45 +++++- src/dtos/appointments.dto.ts | 47 ++++++ src/interfaces/appointments.interface.ts | 40 +++-- src/routes/appointment.route.ts | 108 ++++++++++++- src/services/appointment.service.ts | 82 +++++++++- src/swagger-output.json | 189 +++++++++++++++++++++- src/utils/errorMessages.ts | 8 + src/utils/responseMessages.ts | 6 +- 8 files changed, 500 insertions(+), 25 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index e94ae55..34f1d0a 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -192,7 +192,7 @@ export class AppointmentController { }); }); - public getDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getUpcommingDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; if (!doctorId) { @@ -200,7 +200,7 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + const schedule = await this.appointmentService.getUpcommingDoctorSchedule(doctorId); const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); res.status(200).json({ data: schedule, @@ -248,4 +248,45 @@ export class AppointmentController { ...response }); }); + + public getDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const schedules = await this.appointmentService.getDoctorSchedule(doctorId); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); + res.status(200).json({ + data: schedules, + ...response + }); + + }); + + public editDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const { scheduleId, workingDay, ...body } = req.body; + + // for resolving the mapping issue with the db + const updates = this.appointmentService.convertKeysToSnakeCase(body); + + if (workingDay !== undefined) { + updates.day_of_week = this.appointmentService.getDayOfWeek(workingDay); + } + await this.appointmentService.editDoctorSchedule(doctorId, scheduleId, updates); + const response = createMultiLangMessage(SuccessResponseMessages.SCHEDULE_UPDATED_SUCCESSFULLY); + res.status(200).json({ + ...response + }); + + }); } \ No newline at end of file diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index dff2ed6..8aeddf8 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -81,4 +81,51 @@ export class EnterDoctorScheduleDto { @IsBoolean() @IsNotEmpty() isOnline: boolean; +} + +export class EditDoctorScheduleDto { + @IsUUID() + @IsNotEmpty() + scheduleId: string; + + @IsOptional() + clinicId?: string | null; + + @IsOptional() + @IsInt() + @Min(0) + @Max(6) + workingDay?: number; + + @IsOptional() + @IsString() + startTime?: string; + + @IsOptional() + @IsString() + endTime?: string; + + @IsOptional() + @IsInt() + slotDuration?: number; + + @IsOptional() + @IsInt() + bufferTime?: number; + + @IsOptional() + @IsBoolean() + isOnline?: boolean; + + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @IsOptional() + @IsString() + breakStart?: string; + + @IsOptional() + @IsString() + breakEnd?: string; } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 78408bf..f2f004e 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -1,5 +1,5 @@ import { User } from './users.interface'; -import {AppointmentStatus, DayOfWeek} from '@prisma/client' +import { AppointmentStatus, DayOfWeek } from '@prisma/client' export interface Appointment { id: string; @@ -24,28 +24,28 @@ export interface Appointment { export interface PatientAppointment { id: string; - status: AppointmentStatus; - is_online: boolean; + status: AppointmentStatus; + is_online: boolean; slot_duration: number; doctor_name: string; appointment_date: string; start_time: string; end_time: string; clinic_name: string | null; - clinic_address: string | null; + clinic_address: string | null; } export interface PatientTodayAppointment { id: string; - status: AppointmentStatus; - is_online: boolean; + status: AppointmentStatus; + is_online: boolean; slot_duration: number; doctor_name: string; appointment_date: string; start_time: string; end_time: string; clinic_name: string | null; - clinic_address: string | null; + clinic_address: string | null; position: number; estimatedWaitMinutes: number; patientsAhead: number; @@ -53,7 +53,7 @@ export interface PatientTodayAppointment { export interface DoctorAppointment { id: string; - status: AppointmentStatus; + status: AppointmentStatus; slot_duration: number; patient_name: string; appointment_date: string; @@ -64,23 +64,37 @@ export interface DoctorAppointment { } export interface DoctorScheduleDay { - date: string; + date: string; displayDate: string; appointments: DoctorAppointment[]; } export interface AvailableDay { - date: string; - dayOfWeek: DayOfWeek; - displayDate: string; + date: string; + dayOfWeek: DayOfWeek; + displayDate: string; } export interface TimeSlot { - start: string; + start: string; end: string; available: boolean; online: boolean; } +export interface DoctorSchedule { + id: string; + clinicId: string | null; + dayOfWeek: DayOfWeek; + startTime: string; + endTime: string; + slotDuration: number; + bufferTime: number; + isOnline: boolean; + isActive: boolean; + breakStart: string | null; + breakEnd: string | null; +} + diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 0e06dd3..c1704cd 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -5,7 +5,7 @@ import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto } from "@/dtos/appointments.dto"; +import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto, EditDoctorScheduleDto } from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -585,9 +585,9 @@ export class AppointmentRoute implements Routes { ); this.router.get( - `${this.path}/doctor/schedule`, + `${this.path}/doctor/upcomming-schedule`, /* - #swagger.path = '/appointments/doctor/schedule' + #swagger.path = '/appointments/doctor/upcomming-schedule' #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { @@ -657,7 +657,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, - this.appointmentController.getDoctorSchedule + this.appointmentController.getUpcommingDoctorSchedule ); this.router.post( @@ -705,6 +705,106 @@ export class AppointmentRoute implements Routes { this.appointmentController.enterDoctorSchedule ); + this.router.get( + `${this.path}/doctor/schedule`, + /* + #swagger.path = '/appointments/doctor/schedule' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Get the doctor\'s own schedule' + #swagger.responses[200] = { + description: 'Doctor schedule retrieved successfully', + schema: { + data: [ + { + id: 'schedule-uuid', + clinicId: 'clinic-uuid (optional)', + dayOfWeek: 'MONDAY', + startTime: '09:00', + endTime: '17:00', + slotDuration: 30, + bufferTime: 5, + isOnline: true, + isActive: true, + breakStart: null, + breakEnd: null + } + ], + message: 'Doctor schedule retrieved successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - doctor ID missing' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + */ + AuthMiddleware, + this.appointmentController.getDoctorSchedule + ); + + this.router.patch( + `${this.path}/doctor/schedule`, + /* + #swagger.path = '/appointments/doctor/schedule' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Edit a specific entry in the doctor\'s schedule' + #swagger.parameters['body'] = { + in: 'body', + description: 'Schedule edit details (all fields optional except scheduleId)', + required: true, + schema: { + scheduleId: 'schedule-uuid', + clinicId: 'clinic-uuid (optional)', + workingDay: 1, + startTime: '09:00', + endTime: '17:00', + slotDuration: 30, + bufferTime: 5, + isOnline: true, + isActive: false, + breakStart: '2026-02-01', + breakEnd: '2026-02-22' + } + } + #swagger.responses[200] = { + description: 'Schedule updated successfully', + schema: { + message: 'Schedule updated successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid parameters or conflict' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the doctor' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + ValidationMiddleware(EditDoctorScheduleDto), + this.appointmentController.editDoctorSchedule + ); + this.router.get( `${this.path}/doctor/current-schedule`, /* diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index c2b6cf1..90e98ac 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; @Service() @@ -567,7 +567,7 @@ export class AppointmentService { // penalty to be added later }; - public async getDoctorSchedule(doctorId: string): Promise { + public async getUpcommingDoctorSchedule(doctorId: string): Promise { const now = new Date(); const appointments = await prisma.appointment.findMany({ @@ -722,6 +722,70 @@ export class AppointmentService { }; } + public async getDoctorSchedule(doctorId: string): Promise { + const schedules = await prisma.doctorSchedule.findMany({ + where: { + doctor_id: doctorId, + deleted_at: null, + }, + select: { + id: true, + clinic_id: true, + day_of_week: true, + start_time: true, + end_time: true, + slot_duration: true, + buffer_time: true, + is_online: true, + is_active: true, + break_start: true, + break_end: true, + } + }); + + return schedules.map(schedule => ({ + id: schedule.id, + clinicId: schedule.clinic_id, + dayOfWeek: schedule.day_of_week, + startTime: schedule.start_time, + endTime: schedule.end_time, + slotDuration: schedule.slot_duration, + bufferTime: schedule.buffer_time, + isOnline: schedule.is_online, + isActive: schedule.is_active, + breakStart: schedule.break_start, + breakEnd: schedule.break_end, + })); + } + + public async editDoctorSchedule(doctorId: string, scheduleId: string, updates: any): Promise { + const schedule = await prisma.doctorSchedule.findUnique({ + where: { + id: scheduleId + }, + }); + + if (!schedule) { + const error = createBilingualError(404, ErrorMessages.SCHEDULE_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (schedule.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_SCHEDULE_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.doctorSchedule.update({ + where: { + id: scheduleId + }, + data: { + ...updates, + modified_at: new Date(), + } + }); + } + private generateTimeSlots(startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Omit[] { const slots: Omit[] = []; @@ -760,6 +824,15 @@ export class AppointmentService { return days[jsDay]; } + public convertKeysToSnakeCase>(obj: T): Record { + return Object.entries(obj).reduce((acc, [key, value]) => { + if (value !== undefined) { + acc[this.camelToSnakeCase(key)] = value; + } + return acc; + }, {} as Record); + } + // format date as YYYY-MM-DD private formatDate(date: Date): string { const year = date.getUTCFullYear(); @@ -956,4 +1029,9 @@ export class AppointmentService { throw new HttpException(error.status, error.message, error.messageAr); } } + + private camelToSnakeCase(str: string): string { + return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + } + } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 7b326c1..3bf711b 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4070,7 +4070,7 @@ } } }, - "/appointments/doctor/schedule": { + "/appointments/doctor/upcomming-schedule": { "get": { "tags": [ "Appointments" @@ -4164,7 +4164,9 @@ "description": "Unauthorized - doctor not authenticated" } } - }, + } + }, + "/appointments/doctor/schedule": { "post": { "tags": [ "Appointments" @@ -4241,6 +4243,189 @@ "description": "Unauthorized - doctor not authenticated" } } + }, + "get": { + "tags": [ + "Appointments" + ], + "description": "Get the doctor\\'s own schedule", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor schedule retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "schedule-uuid" + }, + "clinicId": { + "type": "string", + "example": "clinic-uuid (optional)" + }, + "dayOfWeek": { + "type": "string", + "example": "MONDAY" + }, + "startTime": { + "type": "string", + "example": "09:00" + }, + "endTime": { + "type": "string", + "example": "17:00" + }, + "slotDuration": { + "type": "number", + "example": 30 + }, + "bufferTime": { + "type": "number", + "example": 5 + }, + "isOnline": { + "type": "boolean", + "example": true + }, + "isActive": { + "type": "boolean", + "example": true + }, + "breakStart": {}, + "breakEnd": {} + } + } + }, + "message": { + "type": "string", + "example": "Doctor schedule retrieved successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - doctor ID missing" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + } + } + }, + "patch": { + "tags": [ + "Appointments" + ], + "description": "Edit a specific entry in the doctor\\'s schedule", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Schedule edit details (all fields optional except scheduleId)", + "required": true, + "schema": { + "type": "object", + "properties": { + "scheduleId": { + "type": "string", + "example": "schedule-uuid" + }, + "clinicId": { + "type": "string", + "example": "clinic-uuid (optional)" + }, + "workingDay": { + "type": "number", + "example": 1 + }, + "startTime": { + "type": "string", + "example": "09:00" + }, + "endTime": { + "type": "string", + "example": "17:00" + }, + "slotDuration": { + "type": "number", + "example": 30 + }, + "bufferTime": { + "type": "number", + "example": 5 + }, + "isOnline": { + "type": "boolean", + "example": true + }, + "isActive": { + "type": "boolean", + "example": false + }, + "breakStart": { + "type": "string", + "example": "2026-02-01" + }, + "breakEnd": { + "type": "string", + "example": "2026-02-22" + } + } + } + } + ], + "responses": { + "200": { + "description": "Schedule updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Schedule updated successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid parameters or conflict" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - schedule does not belong to the doctor" + }, + "404": { + "description": "Schedule not found" + } + } } }, "/appointments/doctor/current-schedule": { diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 1379ea7..2e82a85 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -207,6 +207,14 @@ export const ErrorMessages = { en: "The maximum allowed delay must not exceed 60 minutes.", ar: "يجب ألا يتجاوز الحد الأقصى للتأجيل المسموح به 60 دقيقة." }, + SCHEDULE_NOT_FOUND: { + en: "Schedule not found", + ar: "لم يتم العثور على الجدول" + }, + UNAUTHORIZED_SCHEDULE_ACCESS: { + en: "You are not authorized to access this schedule", + ar: "غير مصرح لك بالوصول إلى هذا الجدول" + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 49b9195..471dff1 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -176,9 +176,11 @@ export const SuccessResponseMessages = { DOCTOR_SCHEDULE_RETRIEVED: { message_en: 'Doctor schedule retrieved successfully', message_ar: 'تم استرجاع جدول الطبيب بنجاح' + }, + SCHEDULE_UPDATED_SUCCESSFULLY: { + message_en: 'Schedule updated successfully', + message_ar: 'تم تحديث الجدول بنجاح' } - - } interface MultiLangMessageObj { From 9c894e026d25d18e3611a6658be1d3438e40cf62 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Feb 2026 11:55:20 +0200 Subject: [PATCH 122/210] added clinic endpoints for admin view --- src/controllers/admin.controller.ts | 28 ++++++++++ src/dtos/clinics.dto.ts | 12 +++++ src/interfaces/clinics.interface.ts | 3 ++ src/routes/admin.route.ts | 80 +++++++++++++++++++++++++++++ src/services/admin.service.ts | 1 + src/services/clinic.service.ts | 22 +++++++- src/utils/responseMessages.ts | 4 ++ 7 files changed, 148 insertions(+), 2 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 4f10e8f..681d0cb 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -7,9 +7,14 @@ import { RequestWithLanguage } from '@/middlewares/language.middleware'; import { formatSpecializationResponse } from '@/utils/specializationTransform'; import { SpecializationKey } from '@/constants/specializations'; import { createMultiLangMessage, SuccessResponseMessages } from '@/utils/responseMessages'; +import { HttpException } from '@/exceptions/HttpException'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { ClinicService } from '@/services/clinic.service'; +import { ClinicResponseDto } from '@/dtos/clinics.dto'; export class AdminController { public adminService = Container.get(AdminService); + public clinicService = Container.get(ClinicService); public addDoctor = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { const doctorData: AddDoctorFromAdminDto = req.body; @@ -121,4 +126,27 @@ export class AdminController { messageAr: responseMessage.messageAr, }); } + + // Clinic Routes + public getAllClinics = async (req: Request, res: Response, next: NextFunction): Promise => { + + const clinics = await this.clinicService.getAllClinics(); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_RETRIEVED); + res.status(200).json({ + data: clinics, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } + public getClinicById = async (req: Request, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + const clinic = await this.clinicService.getClinicById(clinicId); + if (!clinic) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_RETRIEVED); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr, data: clinic }); + } + } \ No newline at end of file diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts index 97f5966..153ffe7 100644 --- a/src/dtos/clinics.dto.ts +++ b/src/dtos/clinics.dto.ts @@ -38,4 +38,16 @@ export class CreateUpdateClinicRequestDto { @IsNotEmpty() @IsNumber() fees: number; +} + +export class ClinicResponseDto { + public id: string; + public name: string; + public opening_at: string; + public closing_at: string; + public address: string; + public address_maps_link?: string; + public phone: string; + public canPayOnline?: boolean; + public is_active: boolean; } \ No newline at end of file diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index 9e1000f..a3317c1 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -2,6 +2,9 @@ import { User, Doctor } from './users.interface'; export interface Clinic { id: string; + name: string; + phone: string; + canPayOnline?: boolean; is_active: boolean; opening_at: string; closing_at: string; diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index d9a2a1c..d3d2017 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -217,5 +217,85 @@ export class AdminRoute implements Routes { LanguageMiddleware, this.adminController.getDoctorById, ); + + // Clinic routes + this.router.get( + `${this.path}/clinics`, + /* + #swagger.path = '/admin/clinics' + #swagger.method = 'get' + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get clinics successful', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'Clinic Name', + is_active: false, + opening_at: '08:00', + closing_at: '16:00', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + canPayOnline: true + } + ], + messageEn: 'Clinics retrieved successfully', + messageAr: "تم استرجاع بيانات العيادات بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.getAllClinics + ); + this.router.get( + `${this.path}/clinics/:id`, + /* + #swagger.path = '/admin/clinics/{id}' + #swagger.method = 'get' + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic to retrieve', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic retrieved successfully', + schema: { + data: { + id: 'clinic-uuid', + name: 'Clinic Name', + is_active: false, + opening_at: '08:00', + closing_at: '16:00', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + canPayOnline: true + }, + messageEn: 'Clinic retrieved successfully', + messageAr: "تم استرجاع بيانات العيادة بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.getClinicById + ); } } \ No newline at end of file diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index c06cb05..fd100f5 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -7,6 +7,7 @@ import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import { User } from '@/interfaces'; import { SENDER_EMAIL } from '@/config'; import { transporter } from '@/utils/nodeMailerService'; +import { ClinicResponseDto } from '@/dtos/clinics.dto'; // TO BE CHANGED const prisma = new PrismaClient(); diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 8fa6108..889a27f 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -1,4 +1,4 @@ -import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; +import { ClinicResponseDto, CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; @@ -34,6 +34,7 @@ export class ClinicService { phone: clinicData.phone, canPayOnline: clinicData.canPayOnline, created_by: doctorId, + is_active: false, }, select: { id: true, @@ -64,7 +65,7 @@ export class ClinicService { }); } - public async getClinicById(clinicId: string): Promise | null> { + public async getClinicById(clinicId: string): Promise { const clinic = await prisma.clinic.findUnique({ where: { id: clinicId, @@ -182,4 +183,21 @@ export class ClinicService { fees: c.fees })); } + + public async getAllClinics(): Promise { + const clinics = await prisma.clinic.findMany({ + select: { + id: true, + name: true, + address: true, + phone: true, + is_active: true, + opening_at: true, + closing_at: true, + canPayOnline: true, + address_maps_link: true, + } + }); + return clinics; + } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 32c6217..e9d0307 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -80,6 +80,10 @@ export const SuccessResponseMessages = { message_en: "Clinic's doctors retrieved successfully.", message_ar: "تم استرجاع أطباء العيادة بنجاح.", }, + CLINICS_RETRIEVED_SUCCESSFULLY: { + message_en: "Clinics retrieved successfully.", + message_ar: "تم استرجاع بيانات العيادات بنجاح.", + }, // Success messages for Doctors DOCTOR_CREATED_WAITING_VERIFICATION: { From cdff7ec6fe6213802efac82cdda90900d6c50c49 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Feb 2026 12:22:35 +0200 Subject: [PATCH 123/210] set clinic active status endpoint --- src/controllers/admin.controller.ts | 13 ++++++++- src/dtos/clinics.dto.ts | 6 ++++ src/routes/admin.route.ts | 43 +++++++++++++++++++++++++++++ src/services/clinic.service.ts | 38 +++++++++++++++++++------ src/utils/responseMessages.ts | 4 +++ 5 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 681d0cb..6a14916 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -10,7 +10,7 @@ import { createMultiLangMessage, SuccessResponseMessages } from '@/utils/respons import { HttpException } from '@/exceptions/HttpException'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { ClinicService } from '@/services/clinic.service'; -import { ClinicResponseDto } from '@/dtos/clinics.dto'; +import { ClinicActiveStatusResponseDto, ClinicResponseDto } from '@/dtos/clinics.dto'; export class AdminController { public adminService = Container.get(AdminService); @@ -148,5 +148,16 @@ export class AdminController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_RETRIEVED); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr, data: clinic }); } + public setClinicActiveStatus = async (req: Request, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + const { is_active } = req.body; + const updatedClinic: ClinicActiveStatusResponseDto = await this.clinicService.setClinicActiveStatus(clinicId, is_active); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_STATUS_UPDATED); + res.status(200).json({ + data: updatedClinic, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } } \ No newline at end of file diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts index 153ffe7..1c8ad22 100644 --- a/src/dtos/clinics.dto.ts +++ b/src/dtos/clinics.dto.ts @@ -50,4 +50,10 @@ export class ClinicResponseDto { public phone: string; public canPayOnline?: boolean; public is_active: boolean; +} + +export class ClinicActiveStatusResponseDto { + public id: string; + public name: string; + public is_active: boolean; } \ No newline at end of file diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index d3d2017..ccf0071 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -297,5 +297,48 @@ export class AdminRoute implements Routes { RoleMiddleware(Role.ADMIN), this.adminController.getClinicById ); + this.router.patch( + `${this.path}/clinics/:id/set-active-status`, + /* + #swagger.path = '/admin/clinics/{id}/set-active-status' + #swagger.method = 'patch' + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic to set active status', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'set active status', + schema: { + is_active: true + } + required: true + } + #swagger.responses[200] = { + description: 'Clinic active status toggled successfully', + schema: { + data: { + id: 'clinic-uuid', + name: 'Clinic Name', + is_active: true + }, + messageEn: 'Clinic active status toggled successfully', + messageAr: "تم تبديل حالة العيادة بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.setClinicActiveStatus + ); } } \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 802ae80..d58b5ab 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -1,8 +1,10 @@ -import { ClinicResponseDto, CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; +import { ClinicActiveStatusResponseDto, ClinicResponseDto, CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; import { Doctor } from "@prisma/client"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { HttpException } from "@/exceptions/HttpException"; @Service() export class ClinicService { @@ -195,14 +197,14 @@ export class ClinicService { present: true, availability_type: { in: ['OFFLINE', 'BOTH'] - }, + }, }, }, include: { - doctor:{ - include:{ - user:{ - select:{ + doctor: { + include: { + user: { + select: { id: true, name: true }, @@ -220,11 +222,11 @@ export class ClinicService { public async getActiveClinics(): Promise[]> { const clinics = await prisma.clinic.findMany({ - where:{ + where: { is_active: true, deleted_at: null, }, - select:{ + select: { id: true, name: true, opening_at: true, @@ -256,4 +258,24 @@ export class ClinicService { }); return clinics; } + public async setClinicActiveStatus(clinicId: string, is_active: boolean): Promise { + const updatedClinic = await prisma.clinic.update({ + where: { + id: clinicId, + }, + data: { + is_active + }, + select: { + id: true, + name: true, + is_active: true, + } + }); + if (!updatedClinic) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + return updatedClinic; + } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 5104afe..857e4ba 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -84,6 +84,10 @@ export const SuccessResponseMessages = { message_en: "Clinics retrieved successfully.", message_ar: "تم استرجاع بيانات العيادات بنجاح.", }, + CLINIC_STATUS_UPDATED: { + message_en: "Clinic active status updated successfully.", + message_ar: "تم تحديث حالة العيادة بنجاح.", + }, // Success messages for Doctors DOCTOR_CREATED_WAITING_VERIFICATION: { From 0669000ed1a64e0278f7771fd928aaf09a924625 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Feb 2026 12:30:14 +0200 Subject: [PATCH 124/210] swagger updated --- src/routes/admin.route.ts | 2 +- src/swagger-output.json | 245 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+), 1 deletion(-) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index ccf0071..69ae409 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -318,10 +318,10 @@ export class AdminRoute implements Routes { #swagger.parameters['body'] = { in: 'body', description: 'set active status', + required: true, schema: { is_active: true } - required: true } #swagger.responses[200] = { description: 'Clinic active status toggled successfully', diff --git a/src/swagger-output.json b/src/swagger-output.json index 3bf711b..c15c10b 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1791,6 +1791,251 @@ } } }, + "/admin/clinics": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get clinics successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "is_active": { + "type": "boolean", + "example": false + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "16:00" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Clinics retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات العيادات بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/clinics/{id}": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to retrieve" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "is_active": { + "type": "boolean", + "example": false + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "16:00" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + } + } + }, + "messageEn": { + "type": "string", + "example": "Clinic retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات العيادة بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/admin/clinics/{id}/set-active-status": { + "patch": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to set active status" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "set active status", + "required": true, + "schema": { + "type": "object", + "properties": { + "is_active": { + "type": "boolean", + "example": true + } + } + } + } + ], + "responses": { + "200": { + "description": "Clinic active status toggled successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "is_active": { + "type": "boolean", + "example": true + } + } + }, + "messageEn": { + "type": "string", + "example": "Clinic active status toggled successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تبديل حالة العيادة بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/super-admin/admins": { "post": { "tags": [ From f6574c83a98c36d8286dc0ef80d899009f0f61c6 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 5 Feb 2026 15:19:32 +0200 Subject: [PATCH 125/210] doctor schedule by date --- src/controllers/appointment.controller.ts | 28 +++ src/routes/appointment.route.ts | 127 ++++++++++--- src/services/appointment.service.ts | 156 ++++++++++++---- src/swagger-output.json | 211 ++++++++++++++++------ 4 files changed, 406 insertions(+), 116 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 34f1d0a..669f086 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -289,4 +289,32 @@ export class AppointmentController { }); }); + + public getScheduleByDate = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { date } = req.query; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!date) { + const error = createBilingualError(400, ErrorMessages.DATE_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(date as string)) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const scheduleData = await this.appointmentService.getScheduleByDate(doctorId, date as string); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_SCHEDULE_RETRIEVED); + res.status(200).json({ + data: scheduleData, + ...response + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index c1704cd..cbc7929 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -392,40 +392,60 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/patient/today-appointment' #swagger.method = 'get' #swagger.tags = ['Appointments'] + #swagger.summary = 'Get all appointments for the patient today' + #swagger.description = 'Returns all appointments scheduled for today for the patient' #swagger.parameters['Authorization'] = { in: 'cookie', - description: 'Bearer token for authentication (must be a patient)', + description: 'Bearer token for authentication (patient role required)', required: true, type: 'string' } - #swagger.description = 'Get todays appointment for the patient, including queue position and estimated wait time' #swagger.responses[200] = { - description: 'Todays appointment details retrieved successfully', + description: 'Today\'s appointments retrieved successfully', schema: { - data: { - id: 'appointment-uuid', - status: 'CONFIRMED', - slot_duration: 30, - doctor_name: 'Dr. House', - appointment_date: '2026-02-03', - start_time: '09:00', - end_time: '09:30', - clinic_name: 'New Cairo Medical Clinic', - clinic_address: '123 Main Street, Medical Park', - position: 5, - estimated_time: 60, - patients_ahead: 3 - } + success: true, + data: [ + { + id: 'appointment-uuid-1', + status: 'CONFIRMED', + is_online: true, + slot_duration: 30, + doctor_name: 'Dr. House', + appointment_date: '2026-02-05', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park', + position: 5, + estimatedWaitMinutes: 60, + patientsAhead: 3 + }, + { + id: 'appointment-uuid-2', + status: 'CONFIRMED', + is_online: false, + slot_duration: 20, + doctor_name: 'Dr. Wilson', + appointment_date: '2026-02-05', + start_time: '14:30', + end_time: '14:50', + clinic_name: 'Downtown Clinic', + clinic_address: '456 Nile Corniche', + position: null, + estimatedWaitMinutes: null, + patientsAhead: null + } + ] } } #swagger.responses[400] = { - description: 'Bad request - patient ID missing' + description: 'Bad request (invalid authentication or missing required fields)', } #swagger.responses[401] = { - description: 'Unauthorized - patient not authenticated' + description: 'Unauthorized - missing or invalid authentication token', } - #swagger.responses[404] = { - description: 'No appointment found for today' + #swagger.responses[403] = { + description: 'Forbidden - user is not authorized as a patient', } */ AuthMiddleware, @@ -855,11 +875,74 @@ export class AppointmentRoute implements Routes { description: 'Unauthorized - invalid or missing token' } #swagger.responses[400] = { - description: 'Bad request (should rarely happen here)' + description: 'Bad request' } */ AuthMiddleware, this.appointmentController.getCurrentDoctorSchedule ); + + this.router.get( + `${this.path}/doctor/daily-schedule`, + /* + #swagger.path = '/appointments/doctor/daily-schedule' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Get all doctor appointments for a specific date' + #swagger.parameters['date'] = { + in: 'query', + description: 'Date in YYYY-MM-DD format', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Daily schedule retrieved successfully', + schema: { + data: [ + { + id: 'appointment-uuid', + status: 'CONFIRMED', + slot_duration: 30, + patient_name: 'John Sink', + appointment_date: '2026-02-05', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park' + }, + { + id: 'appointment-uuid-2', + status: 'COMPLETED', + slot_duration: 20, + patient_name: 'Jane Hopper', + appointment_date: '2026-02-05', + start_time: '10:15', + end_time: '10:35', + clinic_name: null, + clinic_address: null + } + ], + message: { + en: "Doctor's schedule retrieved successfully", + ar: "تم استرجاع جدول الطبيب بنجاح" + } + } + } + #swagger.responses[400] = { + description: 'Bad request - missing or invalid date parameter, or invalid date format' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + */ + AuthMiddleware, + this.appointmentController.getScheduleByDate + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 90e98ac..375d1c6 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -342,19 +342,27 @@ export class AppointmentService { }; } - public async getTodayAppointment(patientId: string): Promise { + public async getTodayAppointment(patientId: string): Promise { + const result: PatientTodayAppointment[] = []; + const today = new Date(); today.setUTCHours(0, 0, 0, 0); + const endOfToday = new Date(); endOfToday.setUTCHours(23, 59, 59, 999); - const appointment = await prisma.appointment.findFirst({ + const appointments = await prisma.appointment.findMany({ where: { patient_id: patientId, scheduled_time: { gte: today, lte: endOfToday, - } + }, + deleted_at: null, + status: { + in: ['CONFIRMED', 'COMPLETED'] + }, + }, select: { id: true, @@ -377,39 +385,55 @@ export class AppointmentService { address: true, } } + }, + orderBy: { + scheduled_time: 'asc' } }); - if (!appointment) { - return null; + if (appointments.length === 0) { + return []; } + for (const appointment of appointments) { + if (appointment.status === 'CONFIRMED') { + await this.queueService.calculateQueuePosition(appointment.id); - await this.queueService.calculateQueuePosition(appointment.id); - // other transactions could interfere so dont blame me - const refreshed = await prisma.appointment.findUnique({ - where: { id: appointment.id }, - select: { - position: true, - estimated_time: true, - patients_ahead: true, + const refreshed = await prisma.appointment.findUnique({ + where: { id: appointment.id }, + select: { + position: true, + estimated_time: true, + patients_ahead: true, + } + }); + + if (refreshed) { + appointment.position = refreshed.position; + appointment.estimated_time = refreshed.estimated_time; + appointment.patients_ahead = refreshed.patients_ahead; + } } - }); - return { - id: appointment.id, - status: appointment.status, - is_online: appointment.is_online, - slot_duration: appointment.slot_duration, - doctor_name: appointment.doctor.name, - appointment_date: this.formatDate(appointment.scheduled_time), - start_time: this.formatTime(appointment.scheduled_time), - end_time: this.formatTime(appointment.end_time), - clinic_name: appointment.clinic ? appointment.clinic.name : null, - clinic_address: appointment.clinic ? appointment.clinic.address : null, - position: refreshed.position, - estimatedWaitMinutes: refreshed.estimated_time, - patientsAhead: refreshed.patients_ahead, - }; + result.push({ + id: appointment.id, + status: appointment.status, + is_online: appointment.is_online, + slot_duration: appointment.slot_duration, + doctor_name: appointment.doctor.name, + appointment_date: this.formatDate(appointment.scheduled_time), + start_time: this.formatTime(appointment.scheduled_time), + end_time: this.formatTime(appointment.end_time), + clinic_name: appointment.clinic ? appointment.clinic.name : null, + clinic_address: appointment.clinic ? appointment.clinic.address : null, + position: appointment.position, + estimatedWaitMinutes: appointment.estimated_time, + patientsAhead: appointment.patients_ahead + }); + } + + return result; + + } public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { @@ -568,13 +592,16 @@ export class AppointmentService { }; public async getUpcommingDoctorSchedule(doctorId: string): Promise { - const now = new Date(); + // to be changed later --> + const nowUTC = new Date(); + const egyptOffset = 2 * 60 * 60 * 1000; + const now = new Date(nowUTC.getTime() + egyptOffset); const appointments = await prisma.appointment.findMany({ where: { doctor_id: doctorId, scheduled_time: { - gte: new Date(), + gte: now, }, status: 'CONFIRMED', deleted_at: null, @@ -639,7 +666,66 @@ export class AppointmentService { return schedule; } - public async getCurrentDoctorSchedule(doctorId: string): Promise { + public async getScheduleByDate(doctorId: string, date: string): Promise { + const requestedDate = new Date(date); + + const startOfDay = new Date(requestedDate); + startOfDay.setUTCHours(0, 0, 0, 0); + + const endOfDay = new Date(requestedDate); + endOfDay.setUTCHours(23, 59, 59, 999); + + const appointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay + }, + status: { + in: ['CONFIRMED', 'COMPLETED'] + }, + deleted_at: null, + }, + select: { + id: true, + scheduled_time: true, + end_time: true, + slot_duration: true, + status: true, + clinic_id: true, + patient: { + select: { + name: true, + } + }, + clinic: { + select: { + name: true, + address: true, + } + } + }, + orderBy: { + scheduled_time: 'asc' + } + }); + + return appointments.map(appointment => ({ + id: appointment.id, + status: appointment.status, + slot_duration: appointment.slot_duration, + patient_name: appointment.patient.name, + appointment_date: this.formatDate(new Date(appointment.scheduled_time)), + start_time: this.formatTime(new Date(appointment.scheduled_time)), + end_time: this.formatTime(new Date(appointment.end_time)), + clinic_name: appointment.clinic?.name || null, + clinic_address: appointment.clinic?.address || null, + })); + + } + + public async getCurrentDoctorSchedule(doctorId: string): Promise { const startOfDay = new Date(); startOfDay.setUTCHours(0, 0, 0, 0); @@ -680,10 +766,6 @@ export class AppointmentService { } }); - if (appointments.length === 0) { - return null; - } - return appointments.map(app => ({ id: app.id, status: app.status, @@ -1033,5 +1115,5 @@ export class AppointmentService { private camelToSnakeCase(str: string): string { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } - + } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index c15c10b..be936cf 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4010,72 +4010,75 @@ "tags": [ "Appointments" ], - "description": "Get todays appointment for the patient, including queue position and estimated wait time", + "summary": "Get all appointments for the patient today", + "description": "Returns all appointments scheduled for today for the patient", "parameters": [ { "name": "Authorization", "in": "cookie", - "description": "Bearer token for authentication (must be a patient)", + "description": "Bearer token for authentication (patient role required)", "required": true, "type": "string" } ], "responses": { "200": { - "description": "Todays appointment details retrieved successfully", + "description": "Today's appointments retrieved successfully", "schema": { "type": "object", "properties": { + "success": { + "type": "boolean", + "example": true + }, "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "appointment-uuid" - }, - "status": { - "type": "string", - "example": "CONFIRMED" - }, - "slot_duration": { - "type": "number", - "example": 30 - }, - "doctor_name": { - "type": "string", - "example": "Dr. House" - }, - "appointment_date": { - "type": "string", - "example": "2026-02-03" - }, - "start_time": { - "type": "string", - "example": "09:00" - }, - "end_time": { - "type": "string", - "example": "09:30" - }, - "clinic_name": { - "type": "string", - "example": "New Cairo Medical Clinic" - }, - "clinic_address": { - "type": "string", - "example": "123 Main Street, Medical Park" - }, - "position": { - "type": "number", - "example": 5 - }, - "estimated_time": { - "type": "number", - "example": 60 - }, - "patients_ahead": { - "type": "number", - "example": 3 + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid-2" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "is_online": { + "type": "boolean", + "example": false + }, + "slot_duration": { + "type": "number", + "example": 20 + }, + "doctor_name": { + "type": "string", + "example": "Dr. Wilson" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-05" + }, + "start_time": { + "type": "string", + "example": "14:30" + }, + "end_time": { + "type": "string", + "example": "14:50" + }, + "clinic_name": { + "type": "string", + "example": "Downtown Clinic" + }, + "clinic_address": { + "type": "string", + "example": "456 Nile Corniche" + }, + "position": {}, + "estimatedWaitMinutes": {}, + "patientsAhead": {} } } } @@ -4086,13 +4089,13 @@ } }, "400": { - "description": "Bad request - patient ID missing" + "description": "Bad request (invalid authentication or missing required fields)" }, "401": { - "description": "Unauthorized - patient not authenticated" + "description": "Unauthorized - missing or invalid authentication token" }, - "404": { - "description": "No appointment found for today" + "403": { + "description": "Forbidden - user is not authorized as a patient" } } } @@ -4752,7 +4755,7 @@ } }, "400": { - "description": "Bad request (should rarely happen here)" + "description": "Bad request" }, "401": { "description": "Unauthorized - invalid or missing token" @@ -4760,6 +4763,100 @@ } } }, + "/appointments/doctor/daily-schedule": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all doctor appointments for a specific date", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "date", + "in": "query", + "description": "Date in YYYY-MM-DD format", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Daily schedule retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid-2" + }, + "status": { + "type": "string", + "example": "COMPLETED" + }, + "slot_duration": { + "type": "number", + "example": 20 + }, + "patient_name": { + "type": "string", + "example": "Jane Hopper" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-05" + }, + "start_time": { + "type": "string", + "example": "10:15" + }, + "end_time": { + "type": "string", + "example": "10:35" + }, + "clinic_name": {}, + "clinic_address": {} + } + } + }, + "message": { + "type": "object", + "properties": { + "en": { + "type": "string", + "example": "Doctor's schedule retrieved successfully" + }, + "ar": { + "type": "string", + "example": "تم استرجاع جدول الطبيب بنجاح" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing or invalid date parameter, or invalid date format" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + } + } + } + }, "/queue/position/{appointmentId}": { "get": { "tags": [ From b2f9dfd5414fdfa21c14815f3acf6756f9da89be Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Feb 2026 16:40:28 +0200 Subject: [PATCH 126/210] handled updating clinic data and fees endpoints --- src/controllers/clinic.controller.ts | 28 +++- src/dtos/clinics.dto.ts | 6 + src/routes/clinic.route.ts | 48 +++++- src/routes/superAdmin.route.ts | 76 +++++++++ src/services/clinic.service.ts | 68 +++++++- src/swagger-output.json | 236 +++++++++++++++++++++++++++ src/utils/errorMessages.ts | 7 +- src/utils/responseMessages.ts | 4 + 8 files changed, 458 insertions(+), 15 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index d39127f..cf2647e 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -41,11 +41,15 @@ export class ClinicController { res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr, data: clinic }); }); - public updateClinicById = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { + public updateClinicById = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const clinicId = req.params.id; const clinicUpdateData: CreateUpdateClinicRequestDto = req.body; - - const isClinicUpdated = await this.clinicService.updateClinic(clinicId, clinicUpdateData); + const isCreatingDoctor = await this.clinicService.isCreatingDoctorOfClinic(req.user.id, clinicId); + if(!isCreatingDoctor) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_CLINIC_UPDATE); + throw new HttpException(error.status, error.message, error.messageAr); + } + const isClinicUpdated = await this.clinicService.updateClinic(req.user.id, clinicId, clinicUpdateData); if (!isClinicUpdated) { const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); @@ -91,4 +95,22 @@ export class ClinicController { const clinics = await this.clinicService.getActiveClinics(); res.status(200).json({ data: clinics, message: 'Clinics retrieved successfully' }); } + + public updateClinicFeesById = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const clinicId = req.params.id; + const { fees } = req.body; + const isDoctorLinkedToClinic = await this.clinicService.isDoctorLinkedToClinic(req.user.id, clinicId); + if (!isDoctorLinkedToClinic) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_CLINIC_UPDATE); + throw new HttpException(error.status, error.message, error.messageAr); + } + const isFeesUpdated = await this.clinicService.updateClinicFees(req.user.id , clinicId, fees); + + if (!isFeesUpdated) { + const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const responseMessage = createMultiLangMessage(SuccessResponseMessages.CLINIC_FEES_UPDATED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); } diff --git a/src/dtos/clinics.dto.ts b/src/dtos/clinics.dto.ts index 1c8ad22..5b7e144 100644 --- a/src/dtos/clinics.dto.ts +++ b/src/dtos/clinics.dto.ts @@ -56,4 +56,10 @@ export class ClinicActiveStatusResponseDto { public id: string; public name: string; public is_active: boolean; +} + +export class ClinicUpdateFeesDto { + @IsNotEmpty() + @IsNumber() + fees: number; } \ No newline at end of file diff --git a/src/routes/clinic.route.ts b/src/routes/clinic.route.ts index 7c632b5..a77ce19 100644 --- a/src/routes/clinic.route.ts +++ b/src/routes/clinic.route.ts @@ -1,5 +1,5 @@ import { ClinicController } from "@/controllers/clinic.controller"; -import { CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; +import { ClinicUpdateFeesDto, CreateUpdateClinicRequestDto } from "@/dtos/clinics.dto"; import { Routes } from "@/interfaces"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; @@ -128,7 +128,7 @@ export class ClinicRoute implements Routes { address_maps_link: 'https://maps.google.com/?q=456+New+Street', phone: '+1234567891', canPayOnline: false, - fees: 150 + fees: 150, } } #swagger.responses[200] = { @@ -144,6 +144,46 @@ export class ClinicRoute implements Routes { ValidationMiddleware(CreateUpdateClinicRequestDto, true), this.clinicController.updateClinicById ); + + this.router.patch( + `${this.path}/:id/fees`, + /* + #swagger.path = '/clinics/{id}/fees' + #swagger.method = 'patch' + #swagger.tags = ['Clinics'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { + in: 'path', + description: 'The unique identifier of the clinic to update fees for', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Clinic fees update data', + required: true, + schema: { + fees: 200 + } + } + #swagger.responses[200] = { + description: 'Clinic fees updated successfully', + schema: { + messageEn: 'Clinic fees updated successfully', + messageAr: "تم تحديث رسوم العيادة بنجاح" + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + ValidationMiddleware(ClinicUpdateFeesDto), + this.clinicController.updateClinicFeesById + ); this.router.delete( `${this.path}/:id`, @@ -203,7 +243,9 @@ export class ClinicRoute implements Routes { canPayOnline: true, is_active: true, created_at: '2024-01-01T00:00:00.000Z', - fees: 100 + fees: 100, + created_by: 'doctor-uuid', + isOwner: true } ], messageEn: "Doctor's clinics retrieved successfully", diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index d39eeb3..2f1c1e1 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -274,5 +274,81 @@ export class SuperAdminRoute implements Routes { LanguageMiddleware, this.adminController.getDoctorById, ) + + // CLINIC ROUTES + this.router.get( + '/super-admin/clinics', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Get clinics successful', + schema: { + data: [ + { + id: 'clinic-uuid', + name: 'Clinic Name', + is_active: false, + opening_at: '08:00', + closing_at: '16:00', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + canPayOnline: true + } + ], + messageEn: 'Clinics retrieved successfully', + messageAr: "تم استرجاع بيانات العيادات بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.adminController.getAllClinics, + ); + this.router.get( + '/super-admin/clinics/:id', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Clinic ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Clinic retrieved successfully', + schema: { + data: { + id: 'clinic-uuid', + name: 'Clinic Name', + is_active: false, + opening_at: '08:00', + closing_at: '16:00', + address: '123 Main St, City, Country', + address_maps_link: 'https://maps.google.com/?q=123+Main+St,+City,+Country', + phone: '1234567890', + canPayOnline: true + }, + messageEn: 'Clinic retrieved successfully', + messageAr: "تم استرجاع بيانات العيادة بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.adminController.getClinicById, + ); } } \ No newline at end of file diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index d58b5ab..aa1cd41 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -90,16 +90,28 @@ export class ClinicService { return clinic; } - public async updateClinic(clinicId: string, clinicData: CreateUpdateClinicRequestDto): Promise { + public async updateClinic(doctorId: string, clinicId: string, clinicData: CreateUpdateClinicRequestDto): Promise { + const { fees, ...clinicUpdateData } = clinicData; const updatedClinic = await prisma.clinic.update({ where: { id: clinicId, }, data: { - ...clinicData, + ...clinicUpdateData, }, }); - return updatedClinic !== null; + const clinicDoctor = await prisma.clinicDoctor.update({ + where: { + clinic_id_doctor_id: { + clinic_id: clinicId, + doctor_id: doctorId, + } + }, + data: { + fees, + } + }); + return (updatedClinic && clinicDoctor) !== null; } public async isCreatingDoctorOfClinic(doctorId: string, clinicId: string): Promise { @@ -175,16 +187,23 @@ export class ClinicService { is_active: true, canPayOnline: true, created_at: true, + created_by: true, } }, fees: true } }); - - return clinics.map(c => ({ - ...c.clinic, - fees: c.fees - })); + return clinics.map(c => { + let isOwner = true; + if (c.clinic.created_by !== doctorId) { + isOwner = false; + } + return { + ...c.clinic, + fees: c.fees, + isOwner + } + }); } public async getClinicDoctors(clinicId: string): Promise[]> { @@ -278,4 +297,37 @@ export class ClinicService { } return updatedClinic; } + + public async isDoctorLinkedToClinic(doctorId: string, clinicId: string): Promise { + const clinicDoctor = await prisma.clinicDoctor.findUnique({ + where: { + clinic_id_doctor_id: { + clinic_id: clinicId, + doctor_id: doctorId, + } + } + }); + if (clinicDoctor === null) { + return false; + } + return true; + } + + public async updateClinicFees(doctorId: string, clinicId: string, fees: number): Promise { + const clinicDoctor = await prisma.clinicDoctor.update({ + where: { + clinic_id_doctor_id: { + clinic_id: clinicId, + doctor_id: doctorId, + } + }, + data: { + fees, + } + }); + if (clinicDoctor === null) { + return false; + } + return true; + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index c15c10b..3990447 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -2743,6 +2743,174 @@ } } }, + "/super-admin/clinics": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get clinics successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "is_active": { + "type": "boolean", + "example": false + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "16:00" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Clinics retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات العيادات بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "/super-admin/clinics/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Clinic ID" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Clinic retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "Clinic Name" + }, + "is_active": { + "type": "boolean", + "example": false + }, + "opening_at": { + "type": "string", + "example": "08:00" + }, + "closing_at": { + "type": "string", + "example": "16:00" + }, + "address": { + "type": "string", + "example": "123 Main St, City, Country" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+St,+City,+Country" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + } + } + }, + "messageEn": { + "type": "string", + "example": "Clinic retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات العيادة بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/doctors/signup": { "post": { "tags": [ @@ -3178,6 +3346,14 @@ "fees": { "type": "number", "example": 100 + }, + "created_by": { + "type": "string", + "example": "doctor-uuid" + }, + "isOwner": { + "type": "boolean", + "example": true } } } @@ -3418,6 +3594,66 @@ } } }, + "/clinics/{id}/fees": { + "patch": { + "tags": [ + "Clinics" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "The unique identifier of the clinic to update fees for" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Clinic fees update data", + "required": true, + "schema": { + "type": "object", + "properties": { + "fees": { + "type": "number", + "example": 200 + } + } + } + } + ], + "responses": { + "200": { + "description": "Clinic fees updated successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Clinic fees updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث رسوم العيادة بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/appointments/online-doctors": { "get": { "tags": [ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 2e82a85..86e6e80 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -148,7 +148,7 @@ export const ErrorMessages = { en: 'Schedule already exists for this day and clinic', ar: 'الجدول موجود بالفعل لهذا اليوم والعيادة' }, - + // Clinic errors CLINIC_NOT_FOUND: { en: 'Clinic not found', @@ -162,6 +162,11 @@ export const ErrorMessages = { en: 'You are not authorized to delete this clinic', ar: 'ليس لديك صلاحية لحذف هذه العيادة', }, + UNAUTHORIZED_CLINIC_UPDATE: { + en: 'You are not authorized to update this clinic', + ar: 'ليس لديك صلاحية لتحديث هذه العيادة', + }, + // appointments DOCTOR_ID_REQUIRED: { en: 'Doctor ID is required', diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 857e4ba..a4fa352 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -88,6 +88,10 @@ export const SuccessResponseMessages = { message_en: "Clinic active status updated successfully.", message_ar: "تم تحديث حالة العيادة بنجاح.", }, + CLINIC_FEES_UPDATED_SUCCESSFULLY: { + message_en: "Clinic fees updated successfully.", + message_ar: "تم تحديث رسوم العيادة بنجاح.", + }, // Success messages for Doctors DOCTOR_CREATED_WAITING_VERIFICATION: { From 9de5f7b8b5ea8be2d7ae2cc3cacfb5849f91fe00 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 6 Feb 2026 03:34:49 +0200 Subject: [PATCH 127/210] vacation routes for doctor --- src/controllers/appointment.controller.ts | 91 +++++++++++ src/dtos/appointments.dto.ts | 158 ++++++++++--------- src/interfaces/appointments.interface.ts | 9 +- src/routes/appointment.route.ts | 141 ++++++++++++++++- src/services/appointment.service.ts | 136 ++++++++++++++++- src/swagger-output.json | 175 ++++++++++++++++++++++ src/utils/errorMessages.ts | 17 ++- src/utils/responseMessages.ts | 15 +- 8 files changed, 663 insertions(+), 79 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 669f086..cc08d05 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -317,4 +317,95 @@ export class AppointmentController { ...response }); }); + + public checkDoctorVacation = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { scheduleId, startDate, endDate } = req.query; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!scheduleId) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!startDate || !endDate) { + const error = createBilingualError(400, ErrorMessages.VACATION_DATES_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(startDate as string) || !dateRegex.test(endDate as string)) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const start = new Date(startDate as string); + const end = new Date(endDate as string); + + if (start >= end) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_RANGE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const checkResult = await this.appointmentService.checkDoctorVacation(doctorId, scheduleId as string, startDate as string, endDate as string); + + const response = createMultiLangMessage(SuccessResponseMessages.VACATION_CHECK_COMPLETED); + res.status(200).json({ + ...response, + data: checkResult + }); + }) + + + public handleDoctorVacation = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { scheduleId, startDate, endDate } = req.body; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!scheduleId) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!startDate || !endDate) { + const error = createBilingualError(400, ErrorMessages.VACATION_DATES_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + + await this.appointmentService.handleDoctorVacation(doctorId, scheduleId, startDate, endDate); + const response = createMultiLangMessage(SuccessResponseMessages.VACATION_SET_SUCCESSFULLY); + res.status(200).json({ + ...response + }); + }) + + public deleteDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise =>{ + const doctorId = req.user.id; + const { scheduleId } = req.params; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!scheduleId) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.deleteDoctorSchedule(doctorId, scheduleId); + const response = createMultiLangMessage(SuccessResponseMessages.SCHEDULE_DELETED_SUCCESSFULLY); + res.status(200).json({ + ...response + }); + }) } \ No newline at end of file diff --git a/src/dtos/appointments.dto.ts b/src/dtos/appointments.dto.ts index 8aeddf8..dba6774 100644 --- a/src/dtos/appointments.dto.ts +++ b/src/dtos/appointments.dto.ts @@ -53,79 +53,93 @@ export class RescheduleAppointmentByDoctorDto { export class EnterDoctorScheduleDto { - @IsOptional() - clinicId?: string | null; - - @IsInt() - @Min(0) - @Max(6) - @IsNotEmpty() - workingDay: number; - - @IsString() - @IsNotEmpty() - startTime: string; - - @IsString() - @IsNotEmpty() - endTime: string; - - @IsInt() - @IsNotEmpty() - slotDuration: number; - - @IsInt() - @IsOptional() - bufferTime?: number = 0; - - @IsBoolean() - @IsNotEmpty() - isOnline: boolean; + @IsOptional() + clinicId?: string | null; + + @IsInt() + @Min(0) + @Max(6) + @IsNotEmpty() + workingDay: number; + + @IsString() + @IsNotEmpty() + startTime: string; + + @IsString() + @IsNotEmpty() + endTime: string; + + @IsInt() + @IsNotEmpty() + slotDuration: number; + + @IsInt() + @IsOptional() + bufferTime?: number = 0; + + @IsBoolean() + @IsNotEmpty() + isOnline: boolean; } export class EditDoctorScheduleDto { - @IsUUID() - @IsNotEmpty() - scheduleId: string; - - @IsOptional() - clinicId?: string | null; - - @IsOptional() - @IsInt() - @Min(0) - @Max(6) - workingDay?: number; - - @IsOptional() - @IsString() - startTime?: string; - - @IsOptional() - @IsString() - endTime?: string; - - @IsOptional() - @IsInt() - slotDuration?: number; - - @IsOptional() - @IsInt() - bufferTime?: number; - - @IsOptional() - @IsBoolean() - isOnline?: boolean; - - @IsOptional() - @IsBoolean() - isActive?: boolean; - - @IsOptional() - @IsString() - breakStart?: string; - - @IsOptional() - @IsString() - breakEnd?: string; + @IsUUID() + @IsNotEmpty() + scheduleId: string; + + @IsOptional() + clinicId?: string | null; + + @IsOptional() + @IsInt() + @Min(0) + @Max(6) + workingDay?: number; + + @IsOptional() + @IsString() + startTime?: string; + + @IsOptional() + @IsString() + endTime?: string; + + @IsOptional() + @IsInt() + slotDuration?: number; + + @IsOptional() + @IsInt() + bufferTime?: number; + + @IsOptional() + @IsBoolean() + isOnline?: boolean; + + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @IsOptional() + @IsString() + breakStart?: string; + + @IsOptional() + @IsString() + breakEnd?: string; +} + +export class HandleDoctorVacationDto { + @IsUUID() + @IsNotEmpty() + scheduleId: string; + + @IsDateString() + @IsNotEmpty() + startDate: string; + + @IsDateString() + @IsNotEmpty() + endDate: string; } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index f2f004e..c2febf1 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -96,5 +96,12 @@ export interface DoctorSchedule { breakEnd: string | null; } +export interface checkExistingAppointments { + existing: boolean, + numOfAppointments?: number +} - +export interface ConflictingAppointment { + id: string; + scheduled_time: Date; +} \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index cbc7929..e7ee20b 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -5,7 +5,7 @@ import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto, EditDoctorScheduleDto } from "@/dtos/appointments.dto"; +import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto, EditDoctorScheduleDto, HandleDoctorVacationDto } from "@/dtos/appointments.dto"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -944,5 +944,144 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.getScheduleByDate ); + + this.router.get( + `${this.path}/doctor/vacation-check`, + /* + #swagger.path = '/appointments/doctor/vacation-check' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Check for existing appointments in a proposed vacation period for a specific doctor schedule' + #swagger.parameters['scheduleId'] = { + in: 'query', + description: 'Schedule ID', + required: true, + type: 'string' + } + #swagger.parameters['startDate'] = { + in: 'query', + description: 'Vacation start date (YYYY-MM-DD)', + required: true, + type: 'string' + } + #swagger.parameters['endDate'] = { + in: 'query', + description: 'Vacation end date (YYYY-MM-DD)', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Vacation check completed successfully', + schema: { + data: { + existing: true, + numOfAppointments: 3 + }, + message: 'Vacation check completed successfully' + } + } + #swagger.responses[400] = { + description: 'Bad request - missing/invalid dates or schedule ID' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the doctor' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + this.appointmentController.checkDoctorVacation + ); + + this.router.patch( + `${this.path}/doctor/vacation`, + /* + #swagger.path = '/appointments/doctor/vacation' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Set vacation period for a specific doctor schedule. This will automatically cancel any existing confirmed appointments in the period (use vacation-check first to warn the doctor)' + #swagger.parameters['body'] = { + in: 'body', + description: 'Vacation details', + required: true, + schema: { + scheduleId: 'schedule-uuid', + startDate: '2026-03-01', + endDate: '2026-03-15' + } + } + #swagger.responses[200] = { + description: 'Vacation set successfully (any conflicting appointments cancelled)' + } + #swagger.responses[400] = { + description: 'Bad request - invalid dates or date range' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the doctor' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + ValidationMiddleware(HandleDoctorVacationDto), + this.appointmentController.handleDoctorVacation + ); + + this.router.delete( + `${this.path}/doctor/schedule/:scheduleId`, + /* + #swagger.path = '/appointments/doctor/schedule/{scheduleId}' + #swagger.method = 'delete' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Delete a doctor schedule' + #swagger.parameters['scheduleId'] = { + in: 'path', + description: 'Schedule ID to delete', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Schedule deleted successfully', + } + } + #swagger.responses[400] = { + description: 'Bad request - schedule already deleted or has future appointments' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + this.appointmentController.deleteDoctorSchedule + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 375d1c6..c4d4c01 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; @Service() @@ -425,7 +425,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, - position: appointment.position, + position: appointment.position, estimatedWaitMinutes: appointment.estimated_time, patientsAhead: appointment.patients_ahead }); @@ -594,7 +594,7 @@ export class AppointmentService { public async getUpcommingDoctorSchedule(doctorId: string): Promise { // to be changed later --> const nowUTC = new Date(); - const egyptOffset = 2 * 60 * 60 * 1000; + const egyptOffset = 2 * 60 * 60 * 1000; const now = new Date(nowUTC.getTime() + egyptOffset); const appointments = await prisma.appointment.findMany({ @@ -868,6 +868,136 @@ export class AppointmentService { }); } + public async checkDoctorVacation(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { + const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId, breakStart, breakEnd) + + return conflictingAppointments.length > 0 + ? { existing: true, numOfAppointments: conflictingAppointments.length } + : { existing: false }; + } + + public async handleDoctorVacation(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { + const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId, breakStart, breakEnd) + const idsToCancel = conflictingAppointments.map(appointment => appointment.id); + + await prisma.appointment.updateMany({ + where: { + id: { in: idsToCancel }, + }, + data: { + status: 'CANCELLED', + cancelled_by: 'DOCTOR', + deleted_at: new Date(), + modified_at: new Date(), + }, + }); + + await prisma.doctorSchedule.update({ + where: { + id: scheduleId, + }, + data: { + is_active: false, + break_start: breakStart, + break_end: breakEnd, + modified_at: new Date(), + }, + }); + // DONT FORGET LATER --> notify patients + } + + public async deleteDoctorSchedule(doctorId: string, scheduleId: string): Promise { + const schedule = await prisma.doctorSchedule.findUnique({ + where: { + id: scheduleId + }, + select: { + deleted_at: true + } + }) + + if (!schedule) { + const error = createBilingualError(404, ErrorMessages.SCHEDULE_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (schedule.deleted_at) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.doctorSchedule.update({ + where: { + id: scheduleId + }, + data: { + is_active: false, + deleted_at: new Date(), + } + }) + + } + + private async getConflictingAppointments(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { + const schedule = await prisma.doctorSchedule.findUnique({ + where: { + id: scheduleId + }, + select: { + doctor_id: true, + deleted_at: true, + start_time: true, + end_time: true + } + }); + + if (!schedule) { + const error = createBilingualError(404, ErrorMessages.SCHEDULE_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (schedule.deleted_at) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const vacationStart = new Date(breakStart); + vacationStart.setUTCHours(0, 0, 0, 0); + + const vacationEnd = new Date(breakEnd); + vacationEnd.setUTCHours(23, 59, 59, 999); + + // convert everything to minutes since midnight + // 09:00:00 --> ["09", "00", "00"] --> [9, 0, 0] --> startHour = 9, startMin = 0 + const [startHour, startMin] = schedule.start_time.split(':').map(Number); + const scheduleStartMin = (startHour * 60) + startMin; + + const [endHour, endMin] = schedule.end_time.split(':').map(Number); + const scheduleEndMin = (endHour * 60) + endMin; + + const existingAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: vacationStart, + lte: vacationEnd, + }, + status: 'CONFIRMED', + deleted_at: null, + }, + select: { + id: true, + scheduled_time: true + } + }); + + return existingAppointments.filter(appointment => { + const apptMin = appointment.scheduled_time.getUTCHours() * 60 + appointment.scheduled_time.getUTCMinutes(); + return apptMin >= scheduleStartMin && apptMin < scheduleEndMin; + }); + + } + private generateTimeSlots(startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Omit[] { const slots: Omit[] = []; diff --git a/src/swagger-output.json b/src/swagger-output.json index fabcb62..d929a31 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -5093,6 +5093,181 @@ } } }, + "/appointments/doctor/vacation-check": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Check for existing appointments in a proposed vacation period for a specific doctor schedule", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "scheduleId", + "in": "query", + "description": "Schedule ID", + "required": true, + "type": "string" + }, + { + "name": "startDate", + "in": "query", + "description": "Vacation start date (YYYY-MM-DD)", + "required": true, + "type": "string" + }, + { + "name": "endDate", + "in": "query", + "description": "Vacation end date (YYYY-MM-DD)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Vacation check completed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "existing": { + "type": "boolean", + "example": true + }, + "numOfAppointments": { + "type": "number", + "example": 3 + } + } + }, + "message": { + "type": "string", + "example": "Vacation check completed successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing/invalid dates or schedule ID" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - schedule does not belong to the doctor" + }, + "404": { + "description": "Schedule not found" + } + } + } + }, + "/appointments/doctor/vacation": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Set vacation period for a specific doctor schedule. This will automatically cancel any existing confirmed appointments in the period (use vacation-check first to warn the doctor)", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Vacation details", + "required": true, + "schema": { + "type": "object", + "properties": { + "scheduleId": { + "type": "string", + "example": "schedule-uuid" + }, + "startDate": { + "type": "string", + "example": "2026-03-01" + }, + "endDate": { + "type": "string", + "example": "2026-03-15" + } + } + } + } + ], + "responses": { + "200": { + "description": "Vacation set successfully (any conflicting appointments cancelled)" + }, + "400": { + "description": "Bad request - invalid dates or date range" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - schedule does not belong to the doctor" + }, + "404": { + "description": "Schedule not found" + } + } + } + }, + "/appointments/doctor/schedule/{scheduleId}": { + "delete": { + "tags": [ + "Appointments" + ], + "description": "Delete a doctor schedule", + "parameters": [ + { + "name": "scheduleId", + "in": "path", + "required": true, + "type": "string", + "description": "Schedule ID to delete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Schedule deleted successfully" + }, + "400": { + "description": "Bad request - schedule already deleted or has future appointments" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "404": { + "description": "Schedule not found" + } + } + } + }, "/queue/position/{appointmentId}": { "get": { "tags": [ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 86e6e80..74ee094 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -166,7 +166,22 @@ export const ErrorMessages = { en: 'You are not authorized to update this clinic', ar: 'ليس لديك صلاحية لتحديث هذه العيادة', }, - + SCHEDULE_ALREADY_DELETED: { + en: 'This schedule has already been deleted', + ar: 'تم حذف هذا الجدول مسبقًا', + }, + SCHEDULE_ID_REQUIRED: { + en: 'Schedule ID is required', + ar: 'معرف الجدول مطلوب', + }, + VACATION_DATES_REQUIRED: { + en: 'Vacation dates are required', + ar: 'تواريخ الإجازة مطلوبة', + }, + INVALID_DATE_RANGE: { + en: 'Invalid date range', + ar: 'نطاق التاريخ غير صالح', + }, // appointments DOCTOR_ID_REQUIRED: { en: 'Doctor ID is required', diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index a4fa352..517db6c 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -192,7 +192,20 @@ export const SuccessResponseMessages = { SCHEDULE_UPDATED_SUCCESSFULLY: { message_en: 'Schedule updated successfully', message_ar: 'تم تحديث الجدول بنجاح' - } + }, + VACATION_SET_SUCCESSFULLY: { + message_en: 'Vacation set successfully', + message_ar: 'تم تعيين الإجازة بنجاح', + }, + SCHEDULE_DELETED_SUCCESSFULLY: { + message_en: 'Schedule deleted successfully', + message_ar: 'تم حذف الجدول بنجاح', + }, + VACATION_CHECK_COMPLETED: { + message_en: 'Vacation check completed', + message_ar: 'تم فحص الإجازة' + }, + } interface MultiLangMessageObj { From a6e313c5ef4500132a93ec9321b2a10a9d0d8790 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 6 Feb 2026 17:48:38 +0200 Subject: [PATCH 128/210] update schedule deletion --- src/controllers/appointment.controller.ts | 25 ++++- src/routes/appointment.route.ts | 75 ++++++++++++-- src/services/appointment.service.ts | 117 ++++++++++++--------- src/swagger-output.json | 120 ++++++++++++++++++++-- src/utils/responseMessages.ts | 4 + 5 files changed, 272 insertions(+), 69 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index cc08d05..3819d72 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -388,9 +388,32 @@ export class AppointmentController { }); }) + public checkScheduleDeletion = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { scheduleId} = req.query; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!scheduleId) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const checkResult = await this.appointmentService.checkScheduleDeletion(doctorId, scheduleId as string); + + const response = createMultiLangMessage(SuccessResponseMessages.DELETION_CHECK_COMPLETED); + res.status(200).json({ + ...response, + data: checkResult + }); + }) + public deleteDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise =>{ const doctorId = req.user.id; - const { scheduleId } = req.params; + const { scheduleId } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e7ee20b..cba87c7 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -1047,11 +1047,11 @@ export class AppointmentRoute implements Routes { this.appointmentController.handleDoctorVacation ); - this.router.delete( - `${this.path}/doctor/schedule/:scheduleId`, + this.router.get( + `${this.path}/doctor/schedule/deletion-check`, /* - #swagger.path = '/appointments/doctor/schedule/{scheduleId}' - #swagger.method = 'delete' + #swagger.path = '/appointments/doctor/schedule/deletion-check' + #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { in: 'cookie', @@ -1059,23 +1059,80 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Delete a doctor schedule' + #swagger.description = 'Check if a doctor\'s schedule can be deleted by verifying if there are any existing confirmed appointments associated with it' #swagger.parameters['scheduleId'] = { - in: 'path', - description: 'Schedule ID to delete', + in: 'query', + description: 'Schedule ID to check for deletion', required: true, type: 'string' } #swagger.responses[200] = { - description: 'Schedule deleted successfully', + description: 'Deletion check completed successfully', + schema: { + data: { + existing: true, + numOfAppointments: 2 + }, + message: 'Deletion check completed successfully' } } #swagger.responses[400] = { - description: 'Bad request - schedule already deleted or has future appointments' + description: 'Bad request - missing schedule ID or doctor ID' } #swagger.responses[401] = { description: 'Unauthorized - doctor not authenticated' } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the doctor' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + this.appointmentController.checkScheduleDeletion + ); + + this.router.delete( + `${this.path}/doctor/schedule/delete`, + /* + #swagger.path = '/appointments/doctor/schedule/delete' + #swagger.method = 'delete' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Delete a doctor\'s schedule. If there are any appointments linked to this schedule, they will be automatically cancelled' + #swagger.parameters['body'] = { + in: 'body', + description: 'Schedule deletion payload', + required: true, + schema: { + scheduleId: 'schedule-uuid' + } + } + #swagger.responses[200] = { + description: 'Schedule successfully deleted (any associated confirmed appointments were cancelled)', + schema: { + success: true, + message: { + en: "Schedule deleted successfully", + ar: "تم حذف الجدول بنجاح" + } + } + } + #swagger.responses[400] = { + description: 'Bad request - missing scheduleId in body or invalid request' + } + #swagger.responses[401] = { + description: 'Unauthorized - missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the authenticated doctor' + } #swagger.responses[404] = { description: 'Schedule not found' } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index c4d4c01..6574e72 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -7,6 +7,7 @@ import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; +import { start } from 'repl'; @Service() export class AppointmentService { @@ -903,28 +904,31 @@ export class AppointmentService { modified_at: new Date(), }, }); - // DONT FORGET LATER --> notify patients + // DONT FORGET LATER --> notify patients/ penalty + } + + public async checkScheduleDeletion(doctorId: string, scheduleId: string): Promise { + const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId) + return conflictingAppointments.length > 0 + ? { existing: true, numOfAppointments: conflictingAppointments.length } + : { existing: false }; } public async deleteDoctorSchedule(doctorId: string, scheduleId: string): Promise { - const schedule = await prisma.doctorSchedule.findUnique({ + const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId) + const idsToCancel = conflictingAppointments.map(appointment => appointment.id); + + await prisma.appointment.updateMany({ where: { - id: scheduleId + id: { in: idsToCancel }, }, - select: { - deleted_at: true - } - }) - - if (!schedule) { - const error = createBilingualError(404, ErrorMessages.SCHEDULE_NOT_FOUND); - throw new HttpException(error.status, error.message, error.messageAr); - } - - if (schedule.deleted_at) { - const error = createBilingualError(400, ErrorMessages.SCHEDULE_ALREADY_DELETED); - throw new HttpException(error.status, error.message, error.messageAr); - } + data: { + status: 'CANCELLED', + cancelled_by: 'DOCTOR', + deleted_at: new Date(), + modified_at: new Date(), + }, + }); await prisma.doctorSchedule.update({ where: { @@ -935,10 +939,11 @@ export class AppointmentService { deleted_at: new Date(), } }) + // DONT FORGET LATER --> notify patients / penalty } - private async getConflictingAppointments(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { + private async getConflictingAppointments(doctorId: string, scheduleId: string, breakStart?: string, breakEnd?: string): Promise { const schedule = await prisma.doctorSchedule.findUnique({ where: { id: scheduleId @@ -946,8 +951,8 @@ export class AppointmentService { select: { doctor_id: true, deleted_at: true, - start_time: true, - end_time: true + is_online: true, + day_of_week: true } }); @@ -961,41 +966,53 @@ export class AppointmentService { throw new HttpException(error.status, error.message, error.messageAr); } - const vacationStart = new Date(breakStart); - vacationStart.setUTCHours(0, 0, 0, 0); + let existingAppointments: { id: string; scheduled_time: Date }[]; - const vacationEnd = new Date(breakEnd); - vacationEnd.setUTCHours(23, 59, 59, 999); + if (breakStart && breakEnd){ + const vacationStart = new Date(breakStart); + vacationStart.setUTCHours(0, 0, 0, 0); - // convert everything to minutes since midnight - // 09:00:00 --> ["09", "00", "00"] --> [9, 0, 0] --> startHour = 9, startMin = 0 - const [startHour, startMin] = schedule.start_time.split(':').map(Number); - const scheduleStartMin = (startHour * 60) + startMin; + const vacationEnd = new Date(breakEnd); + vacationEnd.setUTCHours(23, 59, 59, 999); - const [endHour, endMin] = schedule.end_time.split(':').map(Number); - const scheduleEndMin = (endHour * 60) + endMin; - - const existingAppointments = await prisma.appointment.findMany({ - where: { - doctor_id: doctorId, - scheduled_time: { - gte: vacationStart, - lte: vacationEnd, + existingAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + status: 'CONFIRMED', + deleted_at: null, + is_online: schedule.is_online, + scheduled_time: { + gte: vacationStart, + lte: vacationEnd, + } }, - status: 'CONFIRMED', - deleted_at: null, - }, - select: { - id: true, - scheduled_time: true - } - }); - - return existingAppointments.filter(appointment => { - const apptMin = appointment.scheduled_time.getUTCHours() * 60 + appointment.scheduled_time.getUTCMinutes(); - return apptMin >= scheduleStartMin && apptMin < scheduleEndMin; - }); + select: { + id: true, + scheduled_time: true + } + }); + } + else { + existingAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + status: 'CONFIRMED', + deleted_at: null, + is_online: schedule.is_online, + }, + select: { + id: true, + scheduled_time: true + } + }); + } + + const confilctingAppointments = existingAppointments.filter((appointment) => { + const apptDay = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); + return apptDay === schedule.day_of_week; + }) + return confilctingAppointments; } private generateTimeSlots(startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Omit[] { diff --git a/src/swagger-output.json b/src/swagger-output.json index d929a31..460fc0a 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -5230,37 +5230,139 @@ } } }, - "/appointments/doctor/schedule/{scheduleId}": { - "delete": { + "/appointments/doctor/schedule/deletion-check": { + "get": { "tags": [ "Appointments" ], - "description": "Delete a doctor schedule", + "description": "Check if a doctor\\'s schedule can be deleted by verifying if there are any existing confirmed appointments associated with it", "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, { "name": "scheduleId", - "in": "path", + "in": "query", + "description": "Schedule ID to check for deletion", "required": true, - "type": "string", - "description": "Schedule ID to delete" + "type": "string" + } + ], + "responses": { + "200": { + "description": "Deletion check completed successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "existing": { + "type": "boolean", + "example": true + }, + "numOfAppointments": { + "type": "number", + "example": 2 + } + } + }, + "message": { + "type": "string", + "example": "Deletion check completed successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing schedule ID or doctor ID" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - schedule does not belong to the doctor" }, + "404": { + "description": "Schedule not found" + } + } + } + }, + "/appointments/doctor/schedule/delete": { + "delete": { + "tags": [ + "Appointments" + ], + "description": "Delete a doctor\\'s schedule. If there are any appointments linked to this schedule, they will be automatically cancelled", + "parameters": [ { "name": "Authorization", "in": "cookie", "description": "Bearer token for authentication (must be a doctor)", "required": true, "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Schedule deletion payload", + "required": true, + "schema": { + "type": "object", + "properties": { + "scheduleId": { + "type": "string", + "example": "schedule-uuid" + } + } + } } ], "responses": { "200": { - "description": "Schedule deleted successfully" + "description": "Schedule successfully deleted (any associated confirmed appointments were cancelled)", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "object", + "properties": { + "en": { + "type": "string", + "example": "Schedule deleted successfully" + }, + "ar": { + "type": "string", + "example": "تم حذف الجدول بنجاح" + } + } + } + }, + "xml": { + "name": "main" + } + } }, "400": { - "description": "Bad request - schedule already deleted or has future appointments" + "description": "Bad request - missing scheduleId in body or invalid request" }, "401": { - "description": "Unauthorized - doctor not authenticated" + "description": "Unauthorized - missing or invalid token" + }, + "403": { + "description": "Forbidden - schedule does not belong to the authenticated doctor" }, "404": { "description": "Schedule not found" diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 517db6c..4537780 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -205,6 +205,10 @@ export const SuccessResponseMessages = { message_en: 'Vacation check completed', message_ar: 'تم فحص الإجازة' }, + DELETION_CHECK_COMPLETED: { + message_en: 'Deletion check completed', + message_ar: 'تم التحقق من الحذف', + }, } From 2ba0e7fe7b306a257732af6760d0d6a5d73184b1 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 6 Feb 2026 18:12:03 +0200 Subject: [PATCH 129/210] make a common route for conflicting appointments --- src/controllers/appointment.controller.ts | 62 ++++++---------- src/routes/appointment.route.ts | 70 ++++-------------- src/services/appointment.service.ts | 17 ++--- src/swagger-output.json | 86 +++-------------------- src/utils/responseMessages.ts | 11 +-- 5 files changed, 51 insertions(+), 195 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 3819d72..7a69e4d 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -318,7 +318,7 @@ export class AppointmentController { }); }); - public checkDoctorVacation = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public checkConflictingAppointments = catchAsync(async(req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { scheduleId, startDate, endDate } = req.query; @@ -332,35 +332,36 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - if (!startDate || !endDate) { - const error = createBilingualError(400, ErrorMessages.VACATION_DATES_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } + if (startDate || endDate) { + if (!startDate || !endDate) { + const error = createBilingualError(400, ErrorMessages.VACATION_DATES_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } - const dateRegex = /^\d{4}-\d{2}-\d{2}$/; - if (!dateRegex.test(startDate as string) || !dateRegex.test(endDate as string)) { - const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); - throw new HttpException(error.status, error.message, error.messageAr); - } + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(startDate as string) || !dateRegex.test(endDate as string)) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } - const start = new Date(startDate as string); - const end = new Date(endDate as string); + const start = new Date(startDate as string); + const end = new Date(endDate as string); - if (start >= end) { - const error = createBilingualError(400, ErrorMessages.INVALID_DATE_RANGE); - throw new HttpException(error.status, error.message, error.messageAr); + if (start >= end) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_RANGE); + throw new HttpException(error.status, error.message, error.messageAr); + } } - const checkResult = await this.appointmentService.checkDoctorVacation(doctorId, scheduleId as string, startDate as string, endDate as string); - - const response = createMultiLangMessage(SuccessResponseMessages.VACATION_CHECK_COMPLETED); + const checkResult = await this.appointmentService.checkConflictingAppointments(doctorId, scheduleId as string, startDate as string | undefined, endDate as string | undefined); + + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_CHECK_COMPLETED); res.status(200).json({ ...response, data: checkResult }); }) - public handleDoctorVacation = catchAsync(async(req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { scheduleId, startDate, endDate } = req.body; @@ -388,29 +389,6 @@ export class AppointmentController { }); }) - public checkScheduleDeletion = catchAsync(async(req: RequestWithUser, res: Response): Promise => { - const doctorId = req.user.id; - const { scheduleId} = req.query; - - if (!doctorId) { - const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - - if (!scheduleId) { - const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); - throw new HttpException(error.status, error.message, error.messageAr); - } - - const checkResult = await this.appointmentService.checkScheduleDeletion(doctorId, scheduleId as string); - - const response = createMultiLangMessage(SuccessResponseMessages.DELETION_CHECK_COMPLETED); - res.status(200).json({ - ...response, - data: checkResult - }); - }) - public deleteDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise =>{ const doctorId = req.user.id; const { scheduleId } = req.body; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index cba87c7..115a129 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -946,9 +946,9 @@ export class AppointmentRoute implements Routes { ); this.router.get( - `${this.path}/doctor/vacation-check`, + `${this.path}/doctor/schedule/check-appointments`, /* - #swagger.path = '/appointments/doctor/vacation-check' + #swagger.path = '/appointments/doctor/schedule/check-appointments' #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { @@ -957,37 +957,37 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Check for existing appointments in a proposed vacation period for a specific doctor schedule' + #swagger.description = 'Check for existing confirmed appointments in a doctor schedule' #swagger.parameters['scheduleId'] = { in: 'query', - description: 'Schedule ID', + description: 'Schedule ID to check', required: true, type: 'string' } #swagger.parameters['startDate'] = { in: 'query', - description: 'Vacation start date (YYYY-MM-DD)', - required: true, + description: 'Optional for vacation. format: YYYY-MM-DD', + required: false, type: 'string' } #swagger.parameters['endDate'] = { in: 'query', - description: 'Vacation end date (YYYY-MM-DD)', - required: true, + description: 'Optional for vacation. format: YYYY-MM-DD', + required: false, type: 'string' } #swagger.responses[200] = { - description: 'Vacation check completed successfully', + description: 'Check completed successfully', schema: { data: { existing: true, numOfAppointments: 3 }, - message: 'Vacation check completed successfully' + message: 'Check completed successfully' } } #swagger.responses[400] = { - description: 'Bad request - missing/invalid dates or schedule ID' + description: 'Bad request - missing/invalid parameters' } #swagger.responses[401] = { description: 'Unauthorized - doctor not authenticated' @@ -1000,7 +1000,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, - this.appointmentController.checkDoctorVacation + this.appointmentController.checkConflictingAppointments ); this.router.patch( @@ -1047,52 +1047,6 @@ export class AppointmentRoute implements Routes { this.appointmentController.handleDoctorVacation ); - this.router.get( - `${this.path}/doctor/schedule/deletion-check`, - /* - #swagger.path = '/appointments/doctor/schedule/deletion-check' - #swagger.method = 'get' - #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication (must be a doctor)', - required: true, - type: 'string' - } - #swagger.description = 'Check if a doctor\'s schedule can be deleted by verifying if there are any existing confirmed appointments associated with it' - #swagger.parameters['scheduleId'] = { - in: 'query', - description: 'Schedule ID to check for deletion', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Deletion check completed successfully', - schema: { - data: { - existing: true, - numOfAppointments: 2 - }, - message: 'Deletion check completed successfully' - } - } - #swagger.responses[400] = { - description: 'Bad request - missing schedule ID or doctor ID' - } - #swagger.responses[401] = { - description: 'Unauthorized - doctor not authenticated' - } - #swagger.responses[403] = { - description: 'Forbidden - schedule does not belong to the doctor' - } - #swagger.responses[404] = { - description: 'Schedule not found' - } - */ - AuthMiddleware, - this.appointmentController.checkScheduleDeletion - ); - this.router.delete( `${this.path}/doctor/schedule/delete`, /* diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 6574e72..b0f7530 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -869,14 +869,15 @@ export class AppointmentService { }); } - public async checkDoctorVacation(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { - const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId, breakStart, breakEnd) + public async checkConflictingAppointments(doctorId: string, scheduleId: string, breakStart?: string, breakEnd?: string): Promise { + const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId, breakStart, breakEnd); return conflictingAppointments.length > 0 ? { existing: true, numOfAppointments: conflictingAppointments.length } : { existing: false }; } + public async handleDoctorVacation(doctorId: string, scheduleId: string, breakStart: string, breakEnd: string): Promise { const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId, breakStart, breakEnd) const idsToCancel = conflictingAppointments.map(appointment => appointment.id); @@ -907,12 +908,6 @@ export class AppointmentService { // DONT FORGET LATER --> notify patients/ penalty } - public async checkScheduleDeletion(doctorId: string, scheduleId: string): Promise { - const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId) - return conflictingAppointments.length > 0 - ? { existing: true, numOfAppointments: conflictingAppointments.length } - : { existing: false }; - } public async deleteDoctorSchedule(doctorId: string, scheduleId: string): Promise { const conflictingAppointments = await this.getConflictingAppointments(doctorId, scheduleId) @@ -968,7 +963,7 @@ export class AppointmentService { let existingAppointments: { id: string; scheduled_time: Date }[]; - if (breakStart && breakEnd){ + if (breakStart && breakEnd) { const vacationStart = new Date(breakStart); vacationStart.setUTCHours(0, 0, 0, 0); @@ -1006,13 +1001,13 @@ export class AppointmentService { } }); } - + const confilctingAppointments = existingAppointments.filter((appointment) => { const apptDay = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); return apptDay === schedule.day_of_week; }) - return confilctingAppointments; + return confilctingAppointments; } private generateTimeSlots(startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Omit[] { diff --git a/src/swagger-output.json b/src/swagger-output.json index 460fc0a..db4f21a 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -5093,12 +5093,12 @@ } } }, - "/appointments/doctor/vacation-check": { + "/appointments/doctor/schedule/check-appointments": { "get": { "tags": [ "Appointments" ], - "description": "Check for existing appointments in a proposed vacation period for a specific doctor schedule", + "description": "Check for existing confirmed appointments in a doctor schedule", "parameters": [ { "name": "Authorization", @@ -5110,28 +5110,28 @@ { "name": "scheduleId", "in": "query", - "description": "Schedule ID", + "description": "Schedule ID to check", "required": true, "type": "string" }, { "name": "startDate", "in": "query", - "description": "Vacation start date (YYYY-MM-DD)", - "required": true, + "description": "Optional for vacation. format: YYYY-MM-DD", + "required": false, "type": "string" }, { "name": "endDate", "in": "query", - "description": "Vacation end date (YYYY-MM-DD)", - "required": true, + "description": "Optional for vacation. format: YYYY-MM-DD", + "required": false, "type": "string" } ], "responses": { "200": { - "description": "Vacation check completed successfully", + "description": "Check completed successfully", "schema": { "type": "object", "properties": { @@ -5150,7 +5150,7 @@ }, "message": { "type": "string", - "example": "Vacation check completed successfully" + "example": "Check completed successfully" } }, "xml": { @@ -5159,7 +5159,7 @@ } }, "400": { - "description": "Bad request - missing/invalid dates or schedule ID" + "description": "Bad request - missing/invalid parameters" }, "401": { "description": "Unauthorized - doctor not authenticated" @@ -5230,72 +5230,6 @@ } } }, - "/appointments/doctor/schedule/deletion-check": { - "get": { - "tags": [ - "Appointments" - ], - "description": "Check if a doctor\\'s schedule can be deleted by verifying if there are any existing confirmed appointments associated with it", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication (must be a doctor)", - "required": true, - "type": "string" - }, - { - "name": "scheduleId", - "in": "query", - "description": "Schedule ID to check for deletion", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Deletion check completed successfully", - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "existing": { - "type": "boolean", - "example": true - }, - "numOfAppointments": { - "type": "number", - "example": 2 - } - } - }, - "message": { - "type": "string", - "example": "Deletion check completed successfully" - } - }, - "xml": { - "name": "main" - } - } - }, - "400": { - "description": "Bad request - missing schedule ID or doctor ID" - }, - "401": { - "description": "Unauthorized - doctor not authenticated" - }, - "403": { - "description": "Forbidden - schedule does not belong to the doctor" - }, - "404": { - "description": "Schedule not found" - } - } - } - }, "/appointments/doctor/schedule/delete": { "delete": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 4537780..d29426a 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -201,15 +201,10 @@ export const SuccessResponseMessages = { message_en: 'Schedule deleted successfully', message_ar: 'تم حذف الجدول بنجاح', }, - VACATION_CHECK_COMPLETED: { - message_en: 'Vacation check completed', - message_ar: 'تم فحص الإجازة' + APPOINTMENTS_CHECK_COMPLETED: { + message_en: 'Appointments check completed', + message_ar: 'تم فحص المواعيد' }, - DELETION_CHECK_COMPLETED: { - message_en: 'Deletion check completed', - message_ar: 'تم التحقق من الحذف', - }, - } interface MultiLangMessageObj { From cf889ab4eefc1795e75e79829f6aff2a37d2e5fe Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 6 Feb 2026 21:51:59 +0200 Subject: [PATCH 130/210] get/clear doctor vacations --- src/controllers/appointment.controller.ts | 54 +++++++-- src/interfaces/appointments.interface.ts | 9 ++ src/routes/appointment.route.ts | 85 ++++++++++++++ src/services/appointment.service.ts | 103 ++++++++++++++++- src/swagger-output.json | 128 ++++++++++++++++++++++ src/utils/responseMessages.ts | 8 ++ 6 files changed, 378 insertions(+), 9 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 7a69e4d..5b3d667 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -249,7 +249,7 @@ export class AppointmentController { }); }); - public getDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public getDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; if (!doctorId) { @@ -266,7 +266,7 @@ export class AppointmentController { }); - public editDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public editDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; if (!doctorId) { @@ -280,7 +280,7 @@ export class AppointmentController { const updates = this.appointmentService.convertKeysToSnakeCase(body); if (workingDay !== undefined) { - updates.day_of_week = this.appointmentService.getDayOfWeek(workingDay); + updates.day_of_week = this.appointmentService.getDayOfWeek(workingDay); } await this.appointmentService.editDoctorSchedule(doctorId, scheduleId, updates); const response = createMultiLangMessage(SuccessResponseMessages.SCHEDULE_UPDATED_SUCCESSFULLY); @@ -290,7 +290,7 @@ export class AppointmentController { }); - public getScheduleByDate = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public getScheduleByDate = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { date } = req.query; @@ -318,7 +318,7 @@ export class AppointmentController { }); }); - public checkConflictingAppointments = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public checkConflictingAppointments = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { scheduleId, startDate, endDate } = req.query; @@ -355,14 +355,14 @@ export class AppointmentController { const checkResult = await this.appointmentService.checkConflictingAppointments(doctorId, scheduleId as string, startDate as string | undefined, endDate as string | undefined); - const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_CHECK_COMPLETED); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_CHECK_COMPLETED); res.status(200).json({ ...response, data: checkResult }); }) - public handleDoctorVacation = catchAsync(async(req: RequestWithUser, res: Response): Promise => { + public handleDoctorVacation = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { scheduleId, startDate, endDate } = req.body; @@ -389,7 +389,7 @@ export class AppointmentController { }); }) - public deleteDoctorSchedule = catchAsync(async(req: RequestWithUser, res: Response): Promise =>{ + public deleteDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { scheduleId } = req.body; @@ -409,4 +409,42 @@ export class AppointmentController { ...response }); }) + + public getDoctorVacations = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const doctorVacations = await this.appointmentService.getDoctorVacations(doctorId) + + const response = createMultiLangMessage(SuccessResponseMessages.DOCTOR_VACATIONS_RETRIEVED); + res.status(200).json({ + ...response, + data: doctorVacations + }); + }) + + public clearDoctorVacation = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { scheduleId } = req.body; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!scheduleId) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.clearDoctorVacation(doctorId, scheduleId); + const response = createMultiLangMessage(SuccessResponseMessages.VACATION_REMOVED_SUCCESSFULLY); + res.status(200).json({ + ...response + }); + }) } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index c2febf1..d2327e8 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -104,4 +104,13 @@ export interface checkExistingAppointments { export interface ConflictingAppointment { id: string; scheduled_time: Date; +} + +export interface DoctorVacations { + scheduleId: string; + dayOfWeek: DayOfWeek; + isOnline: boolean; + breakStart: string; + breakEnd: string; + numOfAppointments: number } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 115a129..cd9e85d 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -1094,5 +1094,90 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.deleteDoctorSchedule ); + + this.router.get( + `${this.path}/doctor/vacation`, + /* + #swagger.path = '/appointments/doctor/vacation' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Get all vacation periods for the doctor\'s schedules' + #swagger.responses[200] = { + description: 'Doctor vacations retrieved successfully', + schema: { + data: [ + { + scheduleId: 'schedule-uuid', + dayOfWeek: 'MONDAY', + isOnline: true, + breakStart: '2026-03-01', + breakEnd: '2026-03-15', + numOfAppointments: 3 + } + ], + message: { + en: 'Doctor vacations retrieved successfully', + ar: 'تم استرجاع إجازات الطبيب بنجاح' + } + } + } + #swagger.responses[400] = { + description: 'Bad request - doctor ID missing' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + */ + + AuthMiddleware, + this.appointmentController.getDoctorVacations + ); + + this.router.patch( + `${this.path}/doctor/vacation/clear`, + /* + #swagger.path = '/appointments/doctor/vacation/clear' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a doctor)', + required: true, + type: 'string' + } + #swagger.description = 'Clear a vacation period from a specific doctor schedule' + #swagger.parameters['body'] = { + in: 'body', + description: 'Schedule ID to clear vacation from', + required: true, + schema: { + scheduleId: 'schedule-uuid' + } + } + #swagger.responses[200] = { + description: 'Vacation cleared successfully', + } + #swagger.responses[400] = { + description: 'Bad request - missing scheduleId, no active vacation, or invalid parameters' + } + #swagger.responses[401] = { + description: 'Unauthorized - doctor not authenticated' + } + #swagger.responses[403] = { + description: 'Forbidden - schedule does not belong to the doctor' + } + #swagger.responses[404] = { + description: 'Schedule not found' + } + */ + AuthMiddleware, + this.appointmentController.clearDoctorVacation + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index b0f7530..1f72440 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; import { start } from 'repl'; @@ -726,6 +726,107 @@ export class AppointmentService { } + public async clearDoctorVacation(doctorId: string, scheduleId: string): Promise { + const schedule = await prisma.doctorSchedule.findUnique({ + where: { + id: scheduleId + }, + }); + + if (!schedule) { + const error = createBilingualError(404, ErrorMessages.SCHEDULE_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (schedule.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_SCHEDULE_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.doctorSchedule.update({ + where: { + id: scheduleId + }, + data: { + is_active: true, + break_start: null, + break_end: null, + modified_at: new Date(), + } + }); + } + + public async getDoctorVacations(doctorId: string): Promise { + const vacations = await prisma.doctorSchedule.findMany({ + where: { + doctor_id: doctorId, + is_active: false, + break_start: { + not: null, + }, + break_end: { + not: null, + }, + deleted_at: null, + }, + select: { + id: true, + day_of_week: true, + is_online: true, + break_start: true, + break_end: true + }, + orderBy: [ + { break_start: 'asc' }, + { break_end: 'asc' }, + ], + }); + + const doctorVacations: DoctorVacations[] = []; + + for (const vacation of vacations) { + const breakStartDate = new Date(vacation.break_start); + breakStartDate.setUTCHours(0, 0, 0, 0); + + const breakEndDate = new Date(vacation.break_end); + breakEndDate.setUTCHours(23, 59, 59, 999); + + const cancelledAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + status: 'CANCELLED', + cancelled_by: 'DOCTOR', + is_online: vacation.is_online, + scheduled_time: { + gte: breakStartDate, + lte: breakEndDate, + }, + deleted_at: { + not: null + } + }, + select: { + scheduled_time: true, + } + }); + const filteredCancelled = cancelledAppointments.filter(appointment => { + const apptDay = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); + return apptDay === vacation.day_of_week; + }); + + doctorVacations.push({ + scheduleId: vacation.id, + dayOfWeek: vacation.day_of_week, + isOnline: vacation.is_online, + breakStart: vacation.break_start, + breakEnd: vacation.break_end, + numOfAppointments: filteredCancelled.length, + }); + } + + return doctorVacations; + } + public async getCurrentDoctorSchedule(doctorId: string): Promise { const startOfDay = new Date(); startOfDay.setUTCHours(0, 0, 0, 0); diff --git a/src/swagger-output.json b/src/swagger-output.json index db4f21a..7d4a663 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -5228,6 +5228,85 @@ "description": "Schedule not found" } } + }, + "get": { + "tags": [ + "Appointments" + ], + "description": "Get all vacation periods for the doctor\\'s schedules", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Doctor vacations retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "scheduleId": { + "type": "string", + "example": "schedule-uuid" + }, + "dayOfWeek": { + "type": "string", + "example": "MONDAY" + }, + "isOnline": { + "type": "boolean", + "example": true + }, + "breakStart": { + "type": "string", + "example": "2026-03-01" + }, + "breakEnd": { + "type": "string", + "example": "2026-03-15" + }, + "numOfAppointments": { + "type": "number", + "example": 3 + } + } + } + }, + "message": { + "type": "object", + "properties": { + "en": { + "type": "string", + "example": "Doctor vacations retrieved successfully" + }, + "ar": { + "type": "string", + "example": "تم استرجاع إجازات الطبيب بنجاح" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - doctor ID missing" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + } + } } }, "/appointments/doctor/schedule/delete": { @@ -5304,6 +5383,55 @@ } } }, + "/appointments/doctor/vacation/clear": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Clear a vacation period from a specific doctor schedule", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a doctor)", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Schedule ID to clear vacation from", + "required": true, + "schema": { + "type": "object", + "properties": { + "scheduleId": { + "type": "string", + "example": "schedule-uuid" + } + } + } + } + ], + "responses": { + "200": { + "description": "Vacation cleared successfully" + }, + "400": { + "description": "Bad request - missing scheduleId, no active vacation, or invalid parameters" + }, + "401": { + "description": "Unauthorized - doctor not authenticated" + }, + "403": { + "description": "Forbidden - schedule does not belong to the doctor" + }, + "404": { + "description": "Schedule not found" + } + } + } + }, "/queue/position/{appointmentId}": { "get": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index d29426a..f5ef6ea 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -205,6 +205,14 @@ export const SuccessResponseMessages = { message_en: 'Appointments check completed', message_ar: 'تم فحص المواعيد' }, + VACATION_REMOVED_SUCCESSFULLY: { + message_en: 'Vacation removed successfully', + message_ar: 'تم حذف الإجازة بنجاح', + }, + DOCTOR_VACATIONS_RETRIEVED: { + message_en: 'Doctor vacations retrieved successfully', + message_ar: 'تم استرجاع إجازات الطبيب بنجاح', + } } interface MultiLangMessageObj { From 7303246b983d7154096856a6e375d7bade2993dc Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 7 Feb 2026 21:41:56 +0200 Subject: [PATCH 131/210] appointments/vacation modifications --- package-lock.json | 18 ++ package.json | 2 + src/app.ts | 3 + src/controllers/appointment.controller.ts | 6 +- src/interfaces/appointments.interface.ts | 15 +- .../migration.sql | 2 + .../migration.sql | 35 +++ src/prisma/schema.prisma | 46 +++- src/routes/appointment.route.ts | 56 +++-- src/services/appointment.service.ts | 216 +++++++++++++++--- src/services/cron.service.ts | 126 ++++++++++ src/swagger-output.json | 73 ++++-- src/utils/errorMessages.ts | 12 + 13 files changed, 515 insertions(+), 95 deletions(-) create mode 100644 src/prisma/migrations/20260206200942_fix_remove_unique_combination_in_doctor_schedule/migration.sql create mode 100644 src/prisma/migrations/20260207120541_add_vacations_table/migration.sql create mode 100644 src/services/cron.service.ts diff --git a/package-lock.json b/package-lock.json index a0b4e17..8270fa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", "multer": "^2.0.2", + "node-cron": "^4.2.1", "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", @@ -57,6 +58,7 @@ "@types/morgan": "^1.9.10", "@types/multer": "^2.0.0", "@types/node": "^24.10.0", + "@types/node-cron": "^3.0.11", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/socket.io": "^3.0.1", @@ -4035,6 +4037,13 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/nodemailer": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.3.tgz", @@ -10281,6 +10290,15 @@ "node": ">=0.1.99" } }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index 8ae4b0c..f396aa5 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.1", "multer": "^2.0.2", + "node-cron": "^4.2.1", "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", @@ -72,6 +73,7 @@ "@types/morgan": "^1.9.10", "@types/multer": "^2.0.0", "@types/node": "^24.10.0", + "@types/node-cron": "^3.0.11", "@types/nodemailer": "^7.0.3", "@types/passport-google-oauth20": "^2.0.17", "@types/socket.io": "^3.0.1", diff --git a/src/app.ts b/src/app.ts index ead4820..33c1f27 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,6 +16,7 @@ import passport from 'passport'; import '@utils/passsportGoogle'; import { createServer, Server as HttpServer } from 'http'; import { SocketService } from '@/services/socket.service'; +import { VacationCronService } from '@/services/cron.service'; export class App { public app: express.Application; @@ -38,6 +39,8 @@ export class App { this.socketService = new SocketService(); this.socketService.initialize(this.httpServer); + VacationCronService.startCronJobs(); + } public listen() { diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 5b3d667..7208a8e 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -427,9 +427,9 @@ export class AppointmentController { }); }) - public clearDoctorVacation = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public cancelDoctorVacation = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; - const { scheduleId } = req.body; + const { vacationId, scheduleId } = req.body; if (!doctorId) { const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); @@ -441,7 +441,7 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - await this.appointmentService.clearDoctorVacation(doctorId, scheduleId); + await this.appointmentService.cancelDoctorVacation(doctorId, vacationId, scheduleId); const response = createMultiLangMessage(SuccessResponseMessages.VACATION_REMOVED_SUCCESSFULLY); res.status(200).json({ ...response diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index d2327e8..64f80ba 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -1,5 +1,5 @@ import { User } from './users.interface'; -import { AppointmentStatus, DayOfWeek } from '@prisma/client' +import { AppointmentStatus, DayOfWeek, VacationStatus } from '@prisma/client' export interface Appointment { id: string; @@ -106,11 +106,20 @@ export interface ConflictingAppointment { scheduled_time: Date; } -export interface DoctorVacations { +export interface Vacations { + vacationId: string; scheduleId: string; + clinicId: string | null; + clinicName: string | null; + clinicAddress: string | null; dayOfWeek: DayOfWeek; isOnline: boolean; + status: VacationStatus; + cancelledAppointments: number; +} + +export interface DoctorVacations { breakStart: string; breakEnd: string; - numOfAppointments: number + vacations: Vacations[]; } \ No newline at end of file diff --git a/src/prisma/migrations/20260206200942_fix_remove_unique_combination_in_doctor_schedule/migration.sql b/src/prisma/migrations/20260206200942_fix_remove_unique_combination_in_doctor_schedule/migration.sql new file mode 100644 index 0000000..9a361f8 --- /dev/null +++ b/src/prisma/migrations/20260206200942_fix_remove_unique_combination_in_doctor_schedule/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "public"."DoctorSchedules_doctor_id_clinic_id_day_of_week_key"; diff --git a/src/prisma/migrations/20260207120541_add_vacations_table/migration.sql b/src/prisma/migrations/20260207120541_add_vacations_table/migration.sql new file mode 100644 index 0000000..7796282 --- /dev/null +++ b/src/prisma/migrations/20260207120541_add_vacations_table/migration.sql @@ -0,0 +1,35 @@ +-- CreateEnum +CREATE TYPE "VacationStatus" AS ENUM ('UPCOMING', 'CURRENT', 'ENDED'); + +-- CreateTable +CREATE TABLE "Vacations" ( + "id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "schedule_id" TEXT NOT NULL, + "start_date" TEXT NOT NULL, + "end_date" TEXT NOT NULL, + "status" "VacationStatus" NOT NULL DEFAULT 'UPCOMING', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "Vacations_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Vacations_doctor_id_idx" ON "Vacations"("doctor_id"); + +-- CreateIndex +CREATE INDEX "Vacations_schedule_id_idx" ON "Vacations"("schedule_id"); + +-- CreateIndex +CREATE INDEX "Vacations_start_date_idx" ON "Vacations"("start_date"); + +-- CreateIndex +CREATE INDEX "Vacations_end_date_idx" ON "Vacations"("end_date"); + +-- AddForeignKey +ALTER TABLE "Vacations" ADD CONSTRAINT "Vacations_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Vacations" ADD CONSTRAINT "Vacations_schedule_id_fkey" FOREIGN KEY ("schedule_id") REFERENCES "DoctorSchedules"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 614d6af..567ffb4 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -68,6 +68,7 @@ model Doctor { clinic_doctors ClinicDoctor[] user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) doctorSchedules DoctorSchedule[] + vacations Vacation[] @@map("Doctor") } @@ -253,30 +254,57 @@ model RefreshToken { } model DoctorSchedule { - id String @id @default(uuid()) + id String @id @default(uuid()) doctor_id String clinic_id String? day_of_week DayOfWeek start_time String end_time String slot_duration Int - buffer_time Int @default(0) - is_online Boolean @default(true) - is_active Boolean @default(true) + buffer_time Int @default(0) + is_online Boolean @default(true) + is_active Boolean @default(true) break_start String? break_end String? - created_at DateTime @default(now()) - modified_at DateTime @updatedAt + created_at DateTime @default(now()) + modified_at DateTime @updatedAt deleted_at DateTime? - clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) - doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + vacations Vacation[] + clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) - @@unique([doctor_id, clinic_id, day_of_week]) @@index([doctor_id]) @@index([clinic_id]) @@map("DoctorSchedules") } +model Vacation { + id String @id @default(uuid()) + doctor_id String + schedule_id String + start_date String + end_date String + status VacationStatus @default(UPCOMING) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + schedule DoctorSchedule @relation(fields: [schedule_id], references: [id], onDelete: Cascade) + + @@index([doctor_id]) + @@index([schedule_id]) + @@index([start_date]) + @@index([end_date]) + @@map("Vacations") +} + +enum VacationStatus { + UPCOMING + CURRENT + ENDED +} + enum ScanLabType { SCAN LAB diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index cd9e85d..120a65a 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -1107,42 +1107,61 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Get all vacation periods for the doctor\'s schedules' + #swagger.description = 'Get all vacation periods for the doctor, grouped by schedule with details including affected appointments' #swagger.responses[200] = { description: 'Doctor vacations retrieved successfully', schema: { data: [ { - scheduleId: 'schedule-uuid', - dayOfWeek: 'MONDAY', - isOnline: true, breakStart: '2026-03-01', breakEnd: '2026-03-15', - numOfAppointments: 3 + vacations: [ + { + vacationId: 'vacation-uuid', + scheduleId: 'schedule-uuid', + clinicId: 'clinic-uuid', + clinicName: 'New Cairo Medical Clinic', + clinicAddress: '123 Main Street, Medical Park', + dayOfWeek: 'MONDAY', + isOnline: false, + status: 'ACTIVE', + cancelledAppointments: 5 + }, + { + vacationId: 'vacation-uuid-2', + scheduleId: 'schedule-uuid-2', + clinicId: null, + clinicName: null, + clinicAddress: null, + dayOfWeek: 'WEDNESDAY', + isOnline: true, + status: 'ACTIVE', + cancelledAppointments: 2 + } + ] } ], message: { - en: 'Doctor vacations retrieved successfully', - ar: 'تم استرجاع إجازات الطبيب بنجاح' + en: "Doctor's vacations retrieved successfully", + ar: "تم استرجاع إجازات الطبيب بنجاح" } } } #swagger.responses[400] = { - description: 'Bad request - doctor ID missing' + description: 'Bad request - doctor ID missing or invalid' } #swagger.responses[401] = { description: 'Unauthorized - doctor not authenticated' } */ - AuthMiddleware, this.appointmentController.getDoctorVacations ); this.router.patch( - `${this.path}/doctor/vacation/clear`, + `${this.path}/doctor/vacation/cancel`, /* - #swagger.path = '/appointments/doctor/vacation/clear' + #swagger.path = '/appointments/doctor/vacation/cancel' #swagger.method = 'patch' #swagger.tags = ['Appointments'] #swagger.parameters['Authorization'] = { @@ -1151,33 +1170,34 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.description = 'Clear a vacation period from a specific doctor schedule' + #swagger.description = 'Cancel a specific vacation period for a doctor schedule' #swagger.parameters['body'] = { in: 'body', - description: 'Schedule ID to clear vacation from', + description: 'Vacation cancellation details', required: true, schema: { + vacationId: 'vacation-uuid', scheduleId: 'schedule-uuid' } } #swagger.responses[200] = { - description: 'Vacation cleared successfully', + description: 'Vacation removed successfully', } #swagger.responses[400] = { - description: 'Bad request - missing scheduleId, no active vacation, or invalid parameters' + description: 'Bad request - missing vacationId or scheduleId, or invalid parameters' } #swagger.responses[401] = { description: 'Unauthorized - doctor not authenticated' } #swagger.responses[403] = { - description: 'Forbidden - schedule does not belong to the doctor' + description: 'Forbidden - vacation or schedule does not belong to the doctor' } #swagger.responses[404] = { - description: 'Schedule not found' + description: 'Vacation or schedule not found' } */ AuthMiddleware, - this.appointmentController.clearDoctorVacation + this.appointmentController.cancelDoctorVacation ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 1f72440..10dc94f 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; import { start } from 'repl'; @@ -509,6 +509,16 @@ export class AppointmentService { const error = createBilingualError(403, ErrorMessages.DOCTOR_NOT_ASSOCIATED_WITH_CLINIC); throw new HttpException(error.status, error.message, error.messageAr); } + + if (isOnline) { + const error = createBilingualError(403, ErrorMessages.EITHER_ONLINE_OR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + } + + if (!clinicId && !isOnline) { + const error = createBilingualError(403, ErrorMessages.EITHER_ONLINE_OR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); } const startMinutes = this.timeStringToMinutes(startTime); @@ -520,12 +530,70 @@ export class AppointmentService { throw new HttpException(error.status, error.message, error.messageAr); } + // prevent time overlap in the same clinic (on different days) + if (clinicId) { + const overlappingClinics = await prisma.doctorSchedule.findMany({ + where: { + doctor_id: doctorId, + clinic_id: { not: clinicId }, + day_of_week: dayOfWeek, + deleted_at: null + }, + select: { + start_time: true, + end_time: true, + } + }); + + for (const schedule of overlappingClinics) { + const existingStartMins = this.timeStringToMinutes(schedule.start_time); + const existingEndMins = this.timeStringToMinutes(schedule.end_time); + + const hasTimeOverlap = (startMinutes < existingEndMins && endMinutes > existingStartMins); + if (hasTimeOverlap) { + const error = createBilingualError(400, ErrorMessages.SCHEDULE_CONFLICT_DIFFERENT_CLINIC); + throw new HttpException(error.status, error.message, error.messageAr); + } + } + } + + const sameDaySchedules = await prisma.doctorSchedule.findMany({ + where: { + doctor_id: doctorId, + day_of_week: dayOfWeek, + deleted_at: null + }, + select: { + start_time: true, + end_time: true, + is_online: true, + clinic_id: true, + } + }); + + for (const schedule of sameDaySchedules) { + if (schedule.is_online === isOnline) { + continue; + } + + const existingStartMinutes = this.timeStringToMinutes(schedule.start_time); + const existingEndMinutes = this.timeStringToMinutes(schedule.end_time); + + const hasTimeOverlap = (startMinutes < existingEndMinutes && endMinutes > existingStartMinutes); + + if (hasTimeOverlap) { + const error = createBilingualError(400, ErrorMessages.ONLINE_OFFLINE_CONFLICT); + throw new HttpException(error.status, error.message, error.messageAr); + } + } + + const existingSchedule = await prisma.doctorSchedule.findFirst({ where: { doctor_id: doctorId, clinic_id: clinicId, day_of_week: dayOfWeek, - deleted_at: null + deleted_at: null, } }); @@ -726,9 +794,10 @@ export class AppointmentService { } - public async clearDoctorVacation(doctorId: string, scheduleId: string): Promise { + public async cancelDoctorVacation(doctorId: string, vacationId: string, scheduleId: string): Promise { const schedule = await prisma.doctorSchedule.findUnique({ where: { + doctor_id: doctorId, id: scheduleId }, }); @@ -754,10 +823,20 @@ export class AppointmentService { modified_at: new Date(), } }); + + await prisma.vacation.update({ + where: { + id: vacationId + }, + data: { + deleted_at: new Date(), + status: 'ENDED', + } + }); } public async getDoctorVacations(doctorId: string): Promise { - const vacations = await prisma.doctorSchedule.findMany({ + const inActiveSchedules = await prisma.doctorSchedule.findMany({ where: { doctor_id: doctorId, is_active: false, @@ -776,54 +855,107 @@ export class AppointmentService { break_start: true, break_end: true }, - orderBy: [ - { break_start: 'asc' }, - { break_end: 'asc' }, - ], + orderBy: { + break_start: 'asc' + } }); - const doctorVacations: DoctorVacations[] = []; + const vacationGroupsMap = new Map(); + for (const schedule of inActiveSchedules) { + const key = `${schedule.break_start}_${schedule.break_end}`; + if (!vacationGroupsMap.has(key)) { + vacationGroupsMap.set(key, []); + } + vacationGroupsMap.get(key)!.push(schedule); + } - for (const vacation of vacations) { - const breakStartDate = new Date(vacation.break_start); - breakStartDate.setUTCHours(0, 0, 0, 0); - const breakEndDate = new Date(vacation.break_end); - breakEndDate.setUTCHours(23, 59, 59, 999); + const doctorVacations: DoctorVacations[] = []; + + for (const [key, schedules] of vacationGroupsMap.entries()) { + const representativeSchedule = schedules[0]; + const allVacations: Vacations[] = []; - const cancelledAppointments = await prisma.appointment.findMany({ + const vacations = await prisma.vacation.findMany({ where: { doctor_id: doctorId, - status: 'CANCELLED', - cancelled_by: 'DOCTOR', - is_online: vacation.is_online, - scheduled_time: { - gte: breakStartDate, - lte: breakEndDate, - }, - deleted_at: { - not: null - } + start_date: representativeSchedule.break_start, + end_date: representativeSchedule.break_end, + deleted_at: null, }, select: { - scheduled_time: true, + id: true, + doctor_id: true, + schedule_id: true, + start_date: true, + end_date: true, + status: true, + schedule: { + select: { + is_online: true, + day_of_week: true, + clinic_id: true, + clinic: { + select: { + name: true, + address: true, + } + } + } + } } }); - const filteredCancelled = cancelledAppointments.filter(appointment => { - const apptDay = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); - return apptDay === vacation.day_of_week; - }); + + for (const vacation of vacations) { + const breakStartDate = new Date(vacation.start_date); + breakStartDate.setUTCHours(0, 0, 0, 0); + + const breakEndDate = new Date(vacation.end_date); + breakEndDate.setUTCHours(23, 59, 59, 999); + + const cancelledAppointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + status: 'CANCELLED', + cancelled_by: 'DOCTOR', + is_online: vacation.schedule.is_online, + scheduled_time: { + gte: breakStartDate, + lte: breakEndDate, + }, + deleted_at: { + not: null + } + }, + select: { + scheduled_time: true, + } + }); + const filteredCancelled = cancelledAppointments.filter(appointment => { + const apptDay = this.getDayOfWeek(appointment.scheduled_time.getUTCDay()); + return apptDay === vacation.schedule.day_of_week; + }); + + allVacations.push({ + vacationId: vacation.id, + scheduleId: vacation.schedule_id, + clinicId: vacation.schedule.clinic_id, + clinicName: vacation.schedule.clinic?.name || null, + clinicAddress: vacation.schedule.clinic?.address || null, + dayOfWeek: vacation.schedule.day_of_week, + isOnline: vacation.schedule.is_online, + status: vacation.status, + cancelledAppointments: filteredCancelled.length, + }) + } doctorVacations.push({ - scheduleId: vacation.id, - dayOfWeek: vacation.day_of_week, - isOnline: vacation.is_online, - breakStart: vacation.break_start, - breakEnd: vacation.break_end, - numOfAppointments: filteredCancelled.length, + breakStart: representativeSchedule.break_start, + breakEnd: representativeSchedule.break_end, + vacations: allVacations }); - } + } return doctorVacations; } @@ -1006,6 +1138,16 @@ export class AppointmentService { modified_at: new Date(), }, }); + + await prisma.vacation.create({ + data: { + doctor_id: doctorId, + schedule_id: scheduleId, + start_date: breakStart, + end_date: breakEnd, + } + + }) // DONT FORGET LATER --> notify patients/ penalty } diff --git a/src/services/cron.service.ts b/src/services/cron.service.ts new file mode 100644 index 0000000..6d3da3a --- /dev/null +++ b/src/services/cron.service.ts @@ -0,0 +1,126 @@ +import cron from 'node-cron'; +import prisma from '@/config/prisma'; +import { Service, Container } from 'typedi'; + +@Service() +export class VacationCronService { + + private static async runScheduledTasks() { + try { + await this.updateVacationStatuses(); + await this.reactivateEndedSchedules(); + } catch (e) { + console.error('error running cron service:', e); + } + } + + static startCronJobs() { + // run every hour + // cron.schedule('0 * * * *', async () => { + // await this.runScheduledTasks(); + // }); + + // run at midnight + cron.schedule('0 0 * * *', async () => { + console.log('[Cron] Running midnight status update'); + await this.runScheduledTasks(); + }); + + this.runScheduledTasks(); + } + + static async runManually() { + return await this.runScheduledTasks(); + } + + private static async updateVacationStatuses() { + try { + // YYYY-MM-DD format + const today = new Date().toISOString().split('T')[0]; + + await prisma.vacation.updateMany({ + where: { + status: 'UPCOMING', + start_date: { + lte: today, + }, + end_date: { + gte: today, + }, + }, + data: { + status: 'CURRENT', + }, + }); + + await prisma.vacation.updateMany({ + where: { + status: 'CURRENT', + end_date: { + lt: today, + }, + }, + data: { + status: 'ENDED', + }, + }); + } + catch (e) { + console.error('vacation status Update Error', e); + throw e; + } + } + + + private static async reactivateEndedSchedules() { + try { + const today = new Date().toISOString().split('T')[0]; + + const endedSchedules = await prisma.doctorSchedule.findMany({ + where: { + is_active: false, + break_end: { + not: null, + lte: today, + }, + }, + include: { + vacations: { + where: { + end_date: { + lt: today, + }, + }, + }, + }, + }); + + for (const schedule of endedSchedules) { + await prisma.doctorSchedule.update({ + where: { id: schedule.id }, + data: { + is_active: true, + break_start: null, + break_end: null, + }, + }); + + await prisma.vacation.updateMany({ + where: { + schedule_id: schedule.id, + status: 'ENDED', + deleted_at: null, + }, + data: { + deleted_at: new Date() + }, + }); + + } + } + catch (e) { + console.error('schedule eeactivation error]', e); + throw e; + } + } +} \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 7d4a663..02191c0 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -5233,7 +5233,7 @@ "tags": [ "Appointments" ], - "description": "Get all vacation periods for the doctor\\'s schedules", + "description": "Get all vacation periods for the doctor, grouped by schedule with details including affected appointments", "parameters": [ { "name": "Authorization", @@ -5254,18 +5254,6 @@ "items": { "type": "object", "properties": { - "scheduleId": { - "type": "string", - "example": "schedule-uuid" - }, - "dayOfWeek": { - "type": "string", - "example": "MONDAY" - }, - "isOnline": { - "type": "boolean", - "example": true - }, "breakStart": { "type": "string", "example": "2026-03-01" @@ -5274,9 +5262,40 @@ "type": "string", "example": "2026-03-15" }, - "numOfAppointments": { - "type": "number", - "example": 3 + "vacations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "vacationId": { + "type": "string", + "example": "vacation-uuid-2" + }, + "scheduleId": { + "type": "string", + "example": "schedule-uuid-2" + }, + "clinicId": {}, + "clinicName": {}, + "clinicAddress": {}, + "dayOfWeek": { + "type": "string", + "example": "WEDNESDAY" + }, + "isOnline": { + "type": "boolean", + "example": true + }, + "status": { + "type": "string", + "example": "ACTIVE" + }, + "cancelledAppointments": { + "type": "number", + "example": 2 + } + } + } } } } @@ -5286,7 +5305,7 @@ "properties": { "en": { "type": "string", - "example": "Doctor vacations retrieved successfully" + "example": "Doctor's vacations retrieved successfully" }, "ar": { "type": "string", @@ -5301,7 +5320,7 @@ } }, "400": { - "description": "Bad request - doctor ID missing" + "description": "Bad request - doctor ID missing or invalid" }, "401": { "description": "Unauthorized - doctor not authenticated" @@ -5383,12 +5402,12 @@ } } }, - "/appointments/doctor/vacation/clear": { + "/appointments/doctor/vacation/cancel": { "patch": { "tags": [ "Appointments" ], - "description": "Clear a vacation period from a specific doctor schedule", + "description": "Cancel a specific vacation period for a doctor schedule", "parameters": [ { "name": "Authorization", @@ -5400,11 +5419,15 @@ { "name": "body", "in": "body", - "description": "Schedule ID to clear vacation from", + "description": "Vacation cancellation details", "required": true, "schema": { "type": "object", "properties": { + "vacationId": { + "type": "string", + "example": "vacation-uuid" + }, "scheduleId": { "type": "string", "example": "schedule-uuid" @@ -5415,19 +5438,19 @@ ], "responses": { "200": { - "description": "Vacation cleared successfully" + "description": "Vacation removed successfully" }, "400": { - "description": "Bad request - missing scheduleId, no active vacation, or invalid parameters" + "description": "Bad request - missing vacationId or scheduleId, or invalid parameters" }, "401": { "description": "Unauthorized - doctor not authenticated" }, "403": { - "description": "Forbidden - schedule does not belong to the doctor" + "description": "Forbidden - vacation or schedule does not belong to the doctor" }, "404": { - "description": "Schedule not found" + "description": "Vacation or schedule not found" } } } diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 74ee094..d249f91 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -235,6 +235,18 @@ export const ErrorMessages = { en: "You are not authorized to access this schedule", ar: "غير مصرح لك بالوصول إلى هذا الجدول" }, + SCHEDULE_CONFLICT_DIFFERENT_CLINIC: { + en: "There is a scheduling conflict on another clinic", + ar: "يوجد تعارض في المواعيد في عيادة اخرى" + }, + ONLINE_OFFLINE_CONFLICT: { + en: "There is a conflict between online and offline schedules", + ar: "يوجد تعارض بين المواعيد الإلكترونية والحضورية" + }, + EITHER_ONLINE_OR_OFFLINE: { + en: "Please choose either online or offline", + ar: "يرجى اختيار إما الإلكتروني أو الحضوري" + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From a4527acd8b579c4edb9e03dadd2277994adabac1 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 8 Feb 2026 16:20:53 +0200 Subject: [PATCH 132/210] running the cron every min --- src/services/cron.service.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/services/cron.service.ts b/src/services/cron.service.ts index 6d3da3a..4f56b2c 100644 --- a/src/services/cron.service.ts +++ b/src/services/cron.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; @Service() export class VacationCronService { - private static async runScheduledTasks() { + private static async runScheduledTasks() { try { await this.updateVacationStatuses(); await this.reactivateEndedSchedules(); @@ -15,24 +15,18 @@ export class VacationCronService { } static startCronJobs() { + // run every min + cron.schedule('* * * * *', async () => { + await this.runScheduledTasks(); + }); // run every hour // cron.schedule('0 * * * *', async () => { // await this.runScheduledTasks(); // }); - // run at midnight - cron.schedule('0 0 * * *', async () => { - console.log('[Cron] Running midnight status update'); - await this.runScheduledTasks(); - }); - this.runScheduledTasks(); } - static async runManually() { - return await this.runScheduledTasks(); - } - private static async updateVacationStatuses() { try { // YYYY-MM-DD format @@ -81,7 +75,7 @@ export class VacationCronService { is_active: false, break_end: { not: null, - lte: today, + lte: today, }, }, include: { @@ -106,10 +100,10 @@ export class VacationCronService { }); await prisma.vacation.updateMany({ - where: { + where: { schedule_id: schedule.id, status: 'ENDED', - deleted_at: null, + deleted_at: null, }, data: { deleted_at: new Date() @@ -117,7 +111,7 @@ export class VacationCronService { }); } - } + } catch (e) { console.error('schedule eeactivation error]', e); throw e; From 370531e7c4496b45499a555f66264525396e8b04 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 8 Feb 2026 20:08:13 +0200 Subject: [PATCH 133/210] exposing general routes --- src/controllers/clinic.controller.ts | 12 ++++++-- src/controllers/doctor.controller.ts | 6 +++- src/routes/appointment.route.ts | 41 ------------------------- src/swagger-output.json | 45 ---------------------------- src/utils/responseMessages.ts | 8 +++-- 5 files changed, 21 insertions(+), 91 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index cf2647e..3824b98 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -87,13 +87,21 @@ export class ClinicController { public getClinicDoctors = async (req: Request, res: Response, next: NextFunction) => { const {clinicId} = req.params; const doctors = await this.clinicService.getClinicDoctors(clinicId); - res.status(200).json({ data: doctors, message: 'Clinic doctors retrieved successfully' }); + const response = createMultiLangMessage(SuccessResponseMessages.CLINIC_DOCTORS_RETRIEVED); + res.status(200).json({ + data: doctors, + ...response + }); } public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { const clinics = await this.clinicService.getActiveClinics(); - res.status(200).json({ data: clinics, message: 'Clinics retrieved successfully' }); + const response = createMultiLangMessage(SuccessResponseMessages.CLINICS_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: clinics, + ...response + }); } public updateClinicFeesById = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 9cb7ebd..ab1c4f3 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -49,6 +49,10 @@ export class DoctorController { public getOnlineDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { const doctors = await this.doctorService.getOnlineDoctors(); - res.status(200).json({ data: doctors, message: 'Online doctors retrieved successfully' }); + const response = createMultiLangMessage(SuccessResponseMessages.DOCTORS_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: doctors, + ...response + }); } } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 120a65a..e61c5a8 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -25,12 +25,6 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/online-doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } #swagger.description = 'Get all available online doctors' #swagger.responses[200] = { description: 'Online doctors retrieved successfully', @@ -45,7 +39,6 @@ export class AppointmentRoute implements Routes { } } */ - AuthMiddleware, this.doctorController.getOnlineDoctors ); @@ -56,12 +49,6 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinics' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } #swagger.description = 'Get all clinics available for booking appointments' #swagger.responses[200] = { description: 'Active clinics retrieved successfully', @@ -82,7 +69,6 @@ export class AppointmentRoute implements Routes { } } */ - AuthMiddleware, this.clinicController.getActiveClinics ); @@ -93,12 +79,6 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinic/{clinicId}/doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' #swagger.parameters['clinicId'] = { in: 'path', @@ -119,7 +99,6 @@ export class AppointmentRoute implements Routes { } } */ - AuthMiddleware, this.clinicController.getClinicDoctors ); @@ -130,12 +109,6 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/doctor/{doctorId}/available-days' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } #swagger.description = 'Get available days for booking with a specific doctor (up to 30 days ahead)' #swagger.parameters['doctorId'] = { in: 'path', @@ -165,11 +138,7 @@ export class AppointmentRoute implements Routes { #swagger.responses[400] = { description: 'Bad request - missing doctor ID or invalid parameters' } - #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' - } */ - AuthMiddleware, this.appointmentController.getAvailableDays ); @@ -180,12 +149,6 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/doctor/{doctorId}/available-slots' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } #swagger.description = 'Get available time slots for a specific doctor on a given date' #swagger.parameters['doctorId'] = { in: 'path', @@ -228,11 +191,7 @@ export class AppointmentRoute implements Routes { #swagger.responses[400] = { description: 'Bad request - missing date, invalid format, or past date' } - #swagger.responses[401] = { - description: 'Unauthorized - user not authenticated' - } */ - AuthMiddleware, this.appointmentController.getAvailableSlots ); diff --git a/src/swagger-output.json b/src/swagger-output.json index 02191c0..5f91517 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3660,15 +3660,6 @@ "Appointments" ], "description": "Get all available online doctors", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], "responses": { "200": { "description": "Online doctors retrieved successfully", @@ -3710,15 +3701,6 @@ "Appointments" ], "description": "Get all clinics available for booking appointments", - "parameters": [ - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], "responses": { "200": { "description": "Active clinics retrieved successfully", @@ -3791,13 +3773,6 @@ "required": true, "type": "string", "description": "Clinic ID" - }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" } ], "responses": { @@ -3849,13 +3824,6 @@ "type": "string", "description": "Doctor ID" }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - }, { "name": "clinicId", "in": "query", @@ -3902,9 +3870,6 @@ }, "400": { "description": "Bad request - missing doctor ID or invalid parameters" - }, - "401": { - "description": "Unauthorized - user not authenticated" } } } @@ -3923,13 +3888,6 @@ "type": "string", "description": "Doctor ID" }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - }, { "name": "date", "in": "query", @@ -3987,9 +3945,6 @@ }, "400": { "description": "Bad request - missing date, invalid format, or past date" - }, - "401": { - "description": "Unauthorized - user not authenticated" } } } diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index f5ef6ea..ea99ceb 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -181,7 +181,7 @@ export const SuccessResponseMessages = { message_en: "Queue position retrieved successfully.", message_ar: "تم استرجاع موقعك في قائمة الانتظار بنجاح.", }, - SCHEDULE_CREATED_SUCCESSFULLY: { + SCHEDULE_CREATED_SUCCESSFULLY: { message_en: 'Schedule created successfully', message_ar: 'تم إنشاء الجدول بنجاح' }, @@ -212,7 +212,11 @@ export const SuccessResponseMessages = { DOCTOR_VACATIONS_RETRIEVED: { message_en: 'Doctor vacations retrieved successfully', message_ar: 'تم استرجاع إجازات الطبيب بنجاح', - } + }, + DOCTORS_RETRIEVED_SUCCESSFULLY: { + message_en: 'Online doctors retrieved successfully', + message_ar: 'تم استرجاع الأطباء المتاحين عبر الإنترنت بنجاح', + }, } interface MultiLangMessageObj { From fffc0f431b8940a886e4728fc7b293a2e5f2abed Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 9 Feb 2026 02:25:43 +0200 Subject: [PATCH 134/210] modify doctors/clinics routes for appointments --- src/controllers/clinic.controller.ts | 34 ++++-- src/controllers/doctor.controller.ts | 27 ++++- src/interfaces/clinics.interface.ts | 11 ++ src/interfaces/doctors.interface.ts | 14 ++- src/routes/appointment.route.ts | 100 +++++++++++++-- src/services/clinic.service.ts | 53 ++++++-- src/services/doctor.service.ts | 146 ++++++++++++++++++++-- src/services/user.service.ts | 11 ++ src/swagger-output.json | 174 +++++++++++++++++++++++++-- src/utils/errorMessages.ts | 4 + 10 files changed, 526 insertions(+), 48 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 3824b98..67c678e 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -7,6 +7,7 @@ import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; import Container from "typedi"; +import { Gender } from "@prisma/client"; export class ClinicController { public clinicService = Container.get(ClinicService); @@ -44,8 +45,8 @@ export class ClinicController { public updateClinicById = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const clinicId = req.params.id; const clinicUpdateData: CreateUpdateClinicRequestDto = req.body; - const isCreatingDoctor = await this.clinicService.isCreatingDoctorOfClinic(req.user.id, clinicId); - if(!isCreatingDoctor) { + const isCreatingDoctor = await this.clinicService.isCreatingDoctorOfClinic(req.user.id, clinicId); + if (!isCreatingDoctor) { const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_CLINIC_UPDATE); throw new HttpException(error.status, error.message, error.messageAr); } @@ -85,18 +86,37 @@ export class ClinicController { }); public getClinicDoctors = async (req: Request, res: Response, next: NextFunction) => { - const {clinicId} = req.params; - const doctors = await this.clinicService.getClinicDoctors(clinicId); + const { clinicId } = req.params; + const { gender, minFees, maxFees } = req.query; + + let validGender: Gender | undefined = undefined; + if (gender && typeof gender === 'string') { + const upperGender = gender.toUpperCase(); + if (Object.values(Gender).includes(upperGender as Gender)) { + validGender = upperGender as Gender; + } + } + + const finalMinFees = minFees && typeof minFees === 'string' ? parseFloat(minFees) : undefined; + const finalMaxFees = maxFees && typeof maxFees === 'string' ? parseFloat(maxFees) : undefined; + + if (finalMinFees > finalMaxFees){ + const error = createBilingualError(404, ErrorMessages.INVALID_FEES_RANGE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const doctors = await this.clinicService.getClinicDoctors(clinicId, validGender, finalMinFees, finalMaxFees); const response = createMultiLangMessage(SuccessResponseMessages.CLINIC_DOCTORS_RETRIEVED); res.status(200).json({ data: doctors, ...response }); - } public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { - const clinics = await this.clinicService.getActiveClinics(); + const { canPayOnline } = req.query; + const payOnline = canPayOnline !== undefined ? canPayOnline === 'true' : undefined; + const clinics = await this.clinicService.getActiveClinics(payOnline); const response = createMultiLangMessage(SuccessResponseMessages.CLINICS_RETRIEVED_SUCCESSFULLY); res.status(200).json({ data: clinics, @@ -112,7 +132,7 @@ export class ClinicController { const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_CLINIC_UPDATE); throw new HttpException(error.status, error.message, error.messageAr); } - const isFeesUpdated = await this.clinicService.updateClinicFees(req.user.id , clinicId, fees); + const isFeesUpdated = await this.clinicService.updateClinicFees(req.user.id, clinicId, fees); if (!isFeesUpdated) { const error = createBilingualError(404, ErrorMessages.CLINIC_NOT_FOUND); diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index ab1c4f3..181e9c2 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -3,9 +3,11 @@ import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequest import { RequestWithUser } from "@/interfaces"; import { DoctorService } from "@/services/doctor.service"; import { UserService } from "@/services/user.service"; +import { HttpException } from "@/exceptions/HttpException"; import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; import { NextFunction, Request, Response } from "express"; import { Container } from "typedi"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; export class DoctorController { @@ -15,7 +17,7 @@ export class DoctorController { public doctorSignup = async (req: Request, res: Response, next: NextFunction) => { const doctorData: DoctorSignupRequestDto = req.body; const doctorFiles = req.files as Express.Multer.File[]; - await this.doctorService.signup(doctorData , doctorFiles); + await this.doctorService.signup(doctorData, doctorFiles); const responseMessage = createMultiLangMessage(SuccessResponseMessages.DOCTOR_CREATED_WAITING_VERIFICATION); res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }; @@ -55,4 +57,27 @@ export class DoctorController { ...response }); } + + public getDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { + const { gender, minFees, maxFees, isOnline } = req.query; + + const finalIsOnline = isOnline !== undefined ? isOnline === 'true' : undefined; + const finalGender = gender as string | undefined; + + const finalMinFees = minFees && typeof minFees === 'string' ? parseFloat(minFees) : undefined; + const finalMaxFees = maxFees && typeof maxFees === 'string' ? parseFloat(maxFees) : undefined; + + if (finalMinFees > finalMaxFees) { + const error = createBilingualError(404, ErrorMessages.INVALID_FEES_RANGE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const doctors = await this.doctorService.getDoctors(finalGender, finalMinFees, finalMaxFees, finalIsOnline); + + const response = createMultiLangMessage(SuccessResponseMessages.DOCTORS_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: doctors, + ...response + }); + } } \ No newline at end of file diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index a3317c1..85b149d 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -34,3 +34,14 @@ export interface ClinicDoctor { clinic: Clinic; doctor: Doctor; } + +export interface DoctorClinics { + id: string; + name: string; + phone: string; + canPayOnline: boolean; + opening_at: string; + closing_at: string; + address: string; + address_maps_link: string +} \ No newline at end of file diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts index c7da247..797e4cb 100644 --- a/src/interfaces/doctors.interface.ts +++ b/src/interfaces/doctors.interface.ts @@ -1,4 +1,5 @@ -import { DoctorAccountStatus, AvailabilityType } from "@prisma/client"; +import { DoctorAccountStatus, AvailabilityType, Gender} from "@prisma/client"; +import { DoctorClinics } from "./clinics.interface"; export interface Doctor { id: string; @@ -20,4 +21,15 @@ export interface DoctorLoginData { specialization: string, account_status: DoctorAccountStatus } +} + +export interface DoctorPersonalData { + id: string; + name: string; + gender: Gender; + age: number; + specialization: string; + phone: string; + fees: number + clinics?: DoctorClinics[] } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e61c5a8..d815a8b 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -20,26 +20,71 @@ export class AppointmentRoute implements Routes { private initializeRoutes() { this.router.get( - `${this.path}/online-doctors`, + `${this.path}/doctors`, /* - #swagger.path = '/appointments/online-doctors' + #swagger.path = '/appointments/doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.description = 'Get all available online doctors' + #swagger.description = 'Get all doctors available for booking appointments' + #swagger.parameters['gender'] = { + in: 'query', + description: 'Filter doctors by gender (MALE or FEMALE)', + required: false, + type: 'string' + } + #swagger.parameters['minFees'] = { + in: 'query', + description: 'Minimum fees filter', + required: false, + type: 'number' + } + #swagger.parameters['maxFees'] = { + in: 'query', + description: 'Maximum fees filter', + required: false, + type: 'number' + } + #swagger.parameters['isOnline'] = { + in: 'query', + description: 'Filter for online availability (true for online, false for offline)', + required: false, + type: 'boolean' + } #swagger.responses[200] = { - description: 'Online doctors retrieved successfully', + description: 'Doctors retrieved successfully', schema: { data: [ { id: 'doctor-uuid', - name: 'House' + name: 'John Doe', + gender: 'MALE', + age: 45, + specialization: 'IMMUNOLOGY', + phone: '+1234567890', + fees: 200, + clinics: [ + { + id: 'clinic-uuid', + name: 'New Cairo Medical Clinic', + phone: '+1234567890', + canPayOnline: true, + opening_at: '09:00', + closing_at: '17:00', + address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.google.com/?q=123+Main+Street' + } + ] } ], - message: 'Online doctors retrieved successfully' + messageEn: 'Doctors retrieved successfully', + messageAr: 'تم استرجاع الأطباء بنجاح' } } + #swagger.responses[400] = { + description: 'Bad request' + } */ - this.doctorController.getOnlineDoctors + this.doctorController.getDoctors ); // get all clinics @@ -50,6 +95,12 @@ export class AppointmentRoute implements Routes { #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.description = 'Get all clinics available for booking appointments' + #swagger.parameters['canPayOnline'] = { + in: 'query', + description: 'Filter clinics by online payment availability', + required: false, + type: 'boolean' + } #swagger.responses[200] = { description: 'Active clinics retrieved successfully', schema: { @@ -79,25 +130,52 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinic/{clinicId}/doctors' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.description = 'Get all doctors who are accepting appointments at a selected clinic' + #swagger.description = 'Get all doctors in a specific clinic' #swagger.parameters['clinicId'] = { in: 'path', description: 'Clinic ID', required: true, type: 'string' } + #swagger.parameters['gender'] = { + in: 'query', + description: 'Filter doctors by gender (MALE or FEMALE)', + required: false, + type: 'string' + } + #swagger.parameters['minFees'] = { + in: 'query', + description: 'Minimum fees filter', + required: false, + type: 'number' + } + #swagger.parameters['maxFees'] = { + in: 'query', + description: 'Maximum fees filter', + required: false, + type: 'number' + } #swagger.responses[200] = { description: 'Clinic doctors retrieved successfully', schema: { data: [ { - id: 'clinic-uuid', - name: 'House' + id: 'doctor-uuid', + name: 'John Doe', + gender: 'MALE', + age: 45, + specialization: 'IMMUNOLOGY', + phone: '+1234567890', + fees: 200 } ], - message: 'Clinic doctors retrieved successfully' + messageEn: 'Clinic doctors retrieved successfully', + messageAr: 'تم استرجاع أطباء العيادة بنجاح' } } + #swagger.responses[400] = { + description: 'Bad request' + } */ this.clinicController.getClinicDoctors ); diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index aa1cd41..b3b7836 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -2,12 +2,15 @@ import { ClinicActiveStatusResponseDto, ClinicResponseDto, CreateUpdateClinicReq import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; -import { Doctor } from "@prisma/client"; +import { DoctorPersonalData } from "@/interfaces/doctors.interface"; +import { Doctor, Gender } from "@prisma/client"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { HttpException } from "@/exceptions/HttpException"; +import { UserService } from "./user.service"; @Service() export class ClinicService { + private userService = new UserService(); private MAX_CLINICS_PER_DOCTOR = 3; public async isDoctorAllowedToCreateClinic(doctorId: string): Promise { @@ -206,26 +209,39 @@ export class ClinicService { }); } - public async getClinicDoctors(clinicId: string): Promise[]> { + public async getClinicDoctors(clinicId: string, gender?: Gender, minFees?: number, maxFees?: number): Promise[]> { const doctors = await prisma.clinicDoctor.findMany({ where: { clinic_id: clinicId, is_accepting: true, + fees: { + ...(minFees !== undefined && { gte: minFees }), + ...(maxFees !== undefined && { lte: maxFees }) + }, doctor: { account_status: 'APPROVED', present: true, availability_type: { in: ['OFFLINE', 'BOTH'] }, + user: { + ...(gender && { gender }), + } + }, }, - include: { + select: { + fees: true, doctor: { - include: { + select: { + specialization: true, user: { select: { id: true, - name: true + name: true, + gender: true, + date_of_birth: true, + phone: true, }, }, }, @@ -233,17 +249,34 @@ export class ClinicService { }, }); - return doctors.map(d => ({ - id: d.doctor.id, - name: d.doctor.user.name, - })); + const results = await Promise.all( + doctors.map(async (doc) => { + const user = doc.doctor.user; + const age = await this.userService.calculateUserAge(user.date_of_birth); + + const doctorData = { + id: user.id, + name: user.name, + gender: user.gender, + age, + specialization: doc.doctor.specialization, + phone: user.phone, + fees: doc.fees, + } satisfies Partial; + + return doctorData; + }) + ); + + return results; } - public async getActiveClinics(): Promise[]> { + public async getActiveClinics(payOnline?: boolean): Promise[]> { const clinics = await prisma.clinic.findMany({ where: { is_active: true, deleted_at: null, + ...(payOnline !== undefined && { canPayOnline: payOnline }), }, select: { id: true, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index d6de675..c5dc14b 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -4,12 +4,15 @@ import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { Doctor, DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; -import { DoctorLoginData } from "@/interfaces/doctors.interface"; +import { DoctorLoginData, DoctorPersonalData } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; import prisma from "@/config/prisma"; import cloudinary from "@/utils/cloudinary"; import { DOCTOR_FILES } from "@/interfaces"; -import fs from "fs"; +import fs from "fs"; +import { AvailabilityType, Gender } from "@prisma/client"; +import { UserService } from "./user.service"; +import { DoctorClinics } from "@/interfaces"; const authService = new AuthService(); @@ -17,6 +20,8 @@ const authService = new AuthService(); @Service() export class DoctorService { + private userService = new UserService(); + public async signup(doctorData: DoctorSignupRequestDto, doctorFiles: {}): Promise { // Check if email already exists const existingUser = await prisma.user.findUnique({ @@ -73,7 +78,7 @@ export class DoctorService { // Upload files and update doctor record with files urls if (doctorFiles && Object.keys(doctorFiles).length > 0) { const doctorFilesArray = Object.values(doctorFiles).flat() as Express.Multer.File[]; - + await this._uploadFiles(doctorFilesArray, createdUserId); } } @@ -231,7 +236,7 @@ export class DoctorService { break; } console.log(`Deleting ${file.path}`); - + fs.unlinkSync(file.path); // Delete local file after upload }); @@ -261,17 +266,17 @@ export class DoctorService { } public async getOnlineDoctors(): Promise[]> { const doctors = await prisma.doctor.findMany({ - where:{ + where: { account_status: 'APPROVED', present: true, availability_type: { in: ['ONLINE', 'BOTH'] } }, - select:{ + select: { id: true, user: { - select:{ + select: { name: true, } } @@ -283,4 +288,129 @@ export class DoctorService { })); } -} \ No newline at end of file + public async getDoctors(gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean): Promise { + const WhereClause: any = { + is_accepting: true, + doctor: { + account_status: DoctorAccountStatus.APPROVED, + } + }; + + if (isOnline !== undefined) { + WhereClause.doctor = { + ...(WhereClause.doctor || {}), + availability_type: isOnline + ? { in: [AvailabilityType.ONLINE, AvailabilityType.BOTH] } + : { in: [AvailabilityType.OFFLINE, AvailabilityType.BOTH] }, + }; + } + + if (minFees !== undefined || maxFees !== undefined) { + WhereClause.fees = {}; + + if (minFees !== undefined) { + WhereClause.fees.gte = minFees; + } + if (maxFees !== undefined) { + WhereClause.fees.lte = maxFees; + } + } + + if (gender) { + const normalized = gender.toUpperCase(); + if (normalized === 'MALE' || normalized === 'FEMALE') { + WhereClause.doctor.user = { + gender: normalized as Gender + }; + } + } + + const doctorClinics = await prisma.clinicDoctor.findMany({ + where: WhereClause, + include: { + doctor: { + include: { + user: { + select: { + id: true, + name: true, + gender: true, + phone: true, + date_of_birth: true, + }, + }, + }, + }, + clinic: { + select: { + id: true, + name: true, + phone: true, + canPayOnline: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + }, + }, + }, + }); + + const doctorGroupsMap = new Map(); + for (const docClinic of doctorClinics) { + const doctorId = docClinic.doctor.user?.id; + if (!doctorId) continue; + + if (!doctorGroupsMap.has(doctorId)) { + doctorGroupsMap.set(doctorId, []); + } + doctorGroupsMap.get(doctorId)!.push(docClinic); + } + + const doctorPersonalData: DoctorPersonalData[] = []; + + for (const [doctorId, clinicRecords] of doctorGroupsMap.entries()) { + const representativeRecord = clinicRecords[0]; + const doctor = representativeRecord.doctor; + const user = doctor.user; + + if (!user) continue; + + const age = await this.userService.calculateUserAge(user.date_of_birth); + const allClinics: DoctorClinics[] = []; + + if (!isOnline){ + for (const record of clinicRecords) { + allClinics.push({ + id: record.clinic.id, + name: record.clinic.name, + phone: record.clinic.phone, + canPayOnline: record.clinic.canPayOnline, + opening_at: record.clinic.opening_at, + closing_at: record.clinic.closing_at, + address: record.clinic.address, + address_maps_link: record.clinic.address_maps_link || "", + }); + } + } + + doctorPersonalData.push({ + id: user.id, + name: user.name, + gender: user.gender, + age, + specialization: doctor.specialization, + phone: user.phone, + fees: representativeRecord.fees, + clinics: allClinics, + }); + } + + return doctorPersonalData; + } + + +} + + + diff --git a/src/services/user.service.ts b/src/services/user.service.ts index 8204232..6202a78 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -75,4 +75,15 @@ export class UserService { }); } } + + public async calculateUserAge(dateOfBirth: Date): Promise { + const today = new Date(); + const birthDate = new Date(dateOfBirth); + let age = today.getFullYear() - birthDate.getFullYear(); + const monthDiff = today.getMonth() - birthDate.getMonth(); + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { + age--; + } + return age; + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 5f91517..d239dca 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3654,15 +3654,45 @@ } } }, - "/appointments/online-doctors": { + "/appointments/doctors": { "get": { "tags": [ "Appointments" ], - "description": "Get all available online doctors", + "description": "Get all doctors available for booking appointments", + "parameters": [ + { + "name": "gender", + "in": "query", + "description": "Filter doctors by gender (MALE or FEMALE)", + "required": false, + "type": "string" + }, + { + "name": "minFees", + "in": "query", + "description": "Minimum fees filter", + "required": false, + "type": "number" + }, + { + "name": "maxFees", + "in": "query", + "description": "Maximum fees filter", + "required": false, + "type": "number" + }, + { + "name": "isOnline", + "in": "query", + "description": "Filter for online availability (true for online, false for offline)", + "required": false, + "type": "boolean" + } + ], "responses": { "200": { - "description": "Online doctors retrieved successfully", + "description": "Doctors retrieved successfully", "schema": { "type": "object", "properties": { @@ -3677,20 +3707,87 @@ }, "name": { "type": "string", - "example": "House" + "example": "John Doe" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "age": { + "type": "number", + "example": 45 + }, + "specialization": { + "type": "string", + "example": "IMMUNOLOGY" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "fees": { + "type": "number", + "example": 200 + }, + "clinics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "clinic-uuid" + }, + "name": { + "type": "string", + "example": "New Cairo Medical Clinic" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, + "opening_at": { + "type": "string", + "example": "09:00" + }, + "closing_at": { + "type": "string", + "example": "17:00" + }, + "address": { + "type": "string", + "example": "123 Main Street, Medical Park" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=123+Main+Street" + } + } + } } } } }, - "message": { + "messageEn": { "type": "string", - "example": "Online doctors retrieved successfully" + "example": "Doctors retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الأطباء بنجاح" } }, "xml": { "name": "main" } } + }, + "400": { + "description": "Bad request" } } } @@ -3701,6 +3798,15 @@ "Appointments" ], "description": "Get all clinics available for booking appointments", + "parameters": [ + { + "name": "canPayOnline", + "in": "query", + "description": "Filter clinics by online payment availability", + "required": false, + "type": "boolean" + } + ], "responses": { "200": { "description": "Active clinics retrieved successfully", @@ -3765,7 +3871,7 @@ "tags": [ "Appointments" ], - "description": "Get all doctors who are accepting appointments at a selected clinic", + "description": "Get all doctors in a specific clinic", "parameters": [ { "name": "clinicId", @@ -3773,6 +3879,27 @@ "required": true, "type": "string", "description": "Clinic ID" + }, + { + "name": "gender", + "in": "query", + "description": "Filter doctors by gender (MALE or FEMALE)", + "required": false, + "type": "string" + }, + { + "name": "minFees", + "in": "query", + "description": "Minimum fees filter", + "required": false, + "type": "number" + }, + { + "name": "maxFees", + "in": "query", + "description": "Maximum fees filter", + "required": false, + "type": "number" } ], "responses": { @@ -3788,24 +3915,51 @@ "properties": { "id": { "type": "string", - "example": "clinic-uuid" + "example": "doctor-uuid" }, "name": { "type": "string", - "example": "House" + "example": "John Doe" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "age": { + "type": "number", + "example": 45 + }, + "specialization": { + "type": "string", + "example": "IMMUNOLOGY" + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "fees": { + "type": "number", + "example": 200 } } } }, - "message": { + "messageEn": { "type": "string", "example": "Clinic doctors retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع أطباء العيادة بنجاح" } }, "xml": { "name": "main" } } + }, + "400": { + "description": "Bad request" } } } diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index d249f91..8bc72b9 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -187,6 +187,10 @@ export const ErrorMessages = { en: 'Doctor ID is required', ar: 'معرف الطبيب مطلوب', }, + INVALID_FEES_RANGE: { + en: 'Invalid fees range.', + ar: 'نطاق الرسوم غير صالح.' + }, PATIENT_ID_REQUIRED: { en: 'Patient ID is required', ar: 'معرف المريض مطلوب', From aec6c0d7eca303ec4fbe2d417ee3ddc579467ddc Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 9 Feb 2026 20:43:40 +0200 Subject: [PATCH 135/210] add profile pic to doctors response --- src/interfaces/doctors.interface.ts | 3 ++- src/routes/appointment.route.ts | 4 +++- src/services/clinic.service.ts | 2 ++ src/services/doctor.service.ts | 2 ++ src/swagger-output.json | 8 ++++++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts index 797e4cb..3c1fd62 100644 --- a/src/interfaces/doctors.interface.ts +++ b/src/interfaces/doctors.interface.ts @@ -30,6 +30,7 @@ export interface DoctorPersonalData { age: number; specialization: string; phone: string; - fees: number + fees: number; + profilePic: string; clinics?: DoctorClinics[] } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index d815a8b..f1abec9 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -62,6 +62,7 @@ export class AppointmentRoute implements Routes { specialization: 'IMMUNOLOGY', phone: '+1234567890', fees: 200, + profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', clinics: [ { id: 'clinic-uuid', @@ -166,7 +167,8 @@ export class AppointmentRoute implements Routes { age: 45, specialization: 'IMMUNOLOGY', phone: '+1234567890', - fees: 200 + fees: 200, + profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', } ], messageEn: 'Clinic doctors retrieved successfully', diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index b3b7836..8f24349 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -242,6 +242,7 @@ export class ClinicService { gender: true, date_of_birth: true, phone: true, + photo_url: true, }, }, }, @@ -262,6 +263,7 @@ export class ClinicService { specialization: doc.doctor.specialization, phone: user.phone, fees: doc.fees, + profilePic: user.photo_url, } satisfies Partial; return doctorData; diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index c5dc14b..a520ea4 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -337,6 +337,7 @@ export class DoctorService { gender: true, phone: true, date_of_birth: true, + photo_url: true, }, }, }, @@ -402,6 +403,7 @@ export class DoctorService { specialization: doctor.specialization, phone: user.phone, fees: representativeRecord.fees, + profilePic: user.photo_url, clinics: allClinics, }); } diff --git a/src/swagger-output.json b/src/swagger-output.json index d239dca..c5b433b 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3729,6 +3729,10 @@ "type": "number", "example": 200 }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" + }, "clinics": { "type": "array", "items": { @@ -3940,6 +3944,10 @@ "fees": { "type": "number", "example": 200 + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" } } } From faf000ebd96ee4008c285a4bc4c93083c3ade105 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 10 Feb 2026 02:26:35 +0200 Subject: [PATCH 136/210] modify returning the clinics to include doctors --- src/controllers/clinic.controller.ts | 2 +- src/controllers/doctor.controller.ts | 9 -- src/interfaces/clinics.interface.ts | 4 +- src/routes/appointment.route.ts | 40 ++++++-- src/services/clinic.service.ts | 141 ++++++++++++++++++++++----- src/services/doctor.service.ts | 23 ----- src/swagger-output.json | 69 ++++++++++--- 7 files changed, 211 insertions(+), 77 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 67c678e..016302f 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -116,7 +116,7 @@ export class ClinicController { public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { const { canPayOnline } = req.query; const payOnline = canPayOnline !== undefined ? canPayOnline === 'true' : undefined; - const clinics = await this.clinicService.getActiveClinics(payOnline); + const clinics = await this.clinicService.getClinics(payOnline); const response = createMultiLangMessage(SuccessResponseMessages.CLINICS_RETRIEVED_SUCCESSFULLY); res.status(200).json({ data: clinics, diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 181e9c2..ef9506c 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -49,15 +49,6 @@ export class DoctorController { res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } - public getOnlineDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { - const doctors = await this.doctorService.getOnlineDoctors(); - const response = createMultiLangMessage(SuccessResponseMessages.DOCTORS_RETRIEVED_SUCCESSFULLY); - res.status(200).json({ - data: doctors, - ...response - }); - } - public getDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { const { gender, minFees, maxFees, isOnline } = req.query; diff --git a/src/interfaces/clinics.interface.ts b/src/interfaces/clinics.interface.ts index 85b149d..c0408ae 100644 --- a/src/interfaces/clinics.interface.ts +++ b/src/interfaces/clinics.interface.ts @@ -1,4 +1,5 @@ import { User, Doctor } from './users.interface'; +import { DoctorPersonalData } from './doctors.interface'; export interface Clinic { id: string; @@ -43,5 +44,6 @@ export interface DoctorClinics { opening_at: string; closing_at: string; address: string; - address_maps_link: string + address_maps_link: string; + doctors?: Partial[]; } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index f1abec9..a2e4fe2 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -95,31 +95,55 @@ export class AppointmentRoute implements Routes { #swagger.path = '/appointments/clinics' #swagger.method = 'get' #swagger.tags = ['Appointments'] - #swagger.description = 'Get all clinics available for booking appointments' - #swagger.parameters['canPayOnline'] = { + #swagger.description = 'Get all active clinics available for booking appointments' + #swagger.parameters['payOnline'] = { in: 'query', - description: 'Filter clinics by online payment availability', + description: 'Filter clinics that support online payment (true) or not (false)', required: false, type: 'boolean' } #swagger.responses[200] = { - description: 'Active clinics retrieved successfully', + description: 'Clinics retrieved successfully', schema: { data: [ { id: 'clinic-uuid', name: 'New Cairo Medical Clinic', - opening_at: '10:00', + phone: '+1234567890', + canPayOnline: true, + opening_at: '09:00', closing_at: '17:00', address: '123 Main Street, Medical Park', address_maps_link: 'https://maps.google.com/?q=123+Main+Street', - phone: '+1234567890', - canPayOnline: true + doctors: [ + { + id: 'doctor-uuid', + name: 'John Doe', + gender: 'MALE', + age: 45, + phone: '+1234567890', + fees: 200, + profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg' + }, + { + id: 'doctor-uuid2', + name: 'House', + gender: 'MALE', + age: 45, + phone: '+1234567890', + fees: 200, + profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg' + } + ] } ], - message: 'Clinics retrieved successfully' + messageEn: 'Clinics retrieved successfully', + messageAr: 'تم استرجاع العيادات بنجاح' } } + #swagger.responses[400] = { + description: 'Bad request' + } */ this.clinicController.getActiveClinics ); diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 8f24349..64b34fe 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -3,10 +3,12 @@ import { Service } from "typedi"; import prisma from "@/config/prisma"; import { Clinic } from "@/interfaces"; import { DoctorPersonalData } from "@/interfaces/doctors.interface"; +import { DoctorClinics } from "@/interfaces/clinics.interface" import { Doctor, Gender } from "@prisma/client"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { HttpException } from "@/exceptions/HttpException"; import { UserService } from "./user.service"; +import { DoctorAccountStatus } from "@prisma/client"; @Service() export class ClinicService { @@ -273,28 +275,28 @@ export class ClinicService { return results; } - public async getActiveClinics(payOnline?: boolean): Promise[]> { - const clinics = await prisma.clinic.findMany({ - where: { - is_active: true, - deleted_at: null, - ...(payOnline !== undefined && { canPayOnline: payOnline }), - }, - select: { - id: true, - name: true, - opening_at: true, - closing_at: true, - address: true, - address_maps_link: true, - phone: true, - canPayOnline: true, - } - }); - return clinics.map(c => ({ - ...c - })); - } + // public async getActiveClinics(payOnline?: boolean): Promise[]> { + // const clinics = await prisma.clinic.findMany({ + // where: { + // is_active: true, + // deleted_at: null, + // ...(payOnline !== undefined && { canPayOnline: payOnline }), + // }, + // select: { + // id: true, + // name: true, + // opening_at: true, + // closing_at: true, + // address: true, + // address_maps_link: true, + // phone: true, + // canPayOnline: true, + // } + // }); + // return clinics.map(c => ({ + // ...c + // })); + // } public async getAllClinics(): Promise { const clinics = await prisma.clinic.findMany({ @@ -365,4 +367,99 @@ export class ClinicService { } return true; } + + public async getClinics(payOnline?: boolean): Promise { + + const clinicDoctors = await prisma.clinicDoctor.findMany({ + where: { + is_accepting: true, + doctor: { + account_status: DoctorAccountStatus.APPROVED, + }, + clinic: { + ...(payOnline !== undefined && { canPayOnline: payOnline }), + } + }, + include: { + doctor: { + include: { + user: { + select: { + id: true, + name: true, + gender: true, + phone: true, + date_of_birth: true, + photo_url: true, + }, + }, + }, + }, + clinic: { + select: { + id: true, + name: true, + phone: true, + canPayOnline: true, + opening_at: true, + closing_at: true, + address: true, + address_maps_link: true, + }, + }, + }, + }); + + const clinicsGroupsMap = new Map(); + for (const docClinic of clinicDoctors) { + const clinicId = docClinic.clinic.id; + if (!clinicId) continue; + + if (!clinicsGroupsMap.has(clinicId)) { + clinicsGroupsMap.set(clinicId, []); + } + clinicsGroupsMap.get(clinicId)!.push(docClinic); + } + + const clinicsData: DoctorClinics[] = []; + + for (const [clinicId, doctorRecords] of clinicsGroupsMap.entries()) { + const representativeRecord = doctorRecords[0]; + const clinic = representativeRecord.clinic; + const user = representativeRecord.doctor.user; + + if (!user) continue; + + + const allDoctors: Partial[] = []; + + for (const record of doctorRecords) { + const age = await this.userService.calculateUserAge(record.doctor.user.date_of_birth); + allDoctors.push({ + id: record.doctor.user.id, + name: record.doctor.user.name, + gender: record.doctor.user.gender, + age, + phone: record.doctor.user.phone, + fees: representativeRecord.fees, + profilePic: record.doctor.user.photo_url, + }); + } + + clinicsData.push({ + id: clinic.id, + name: clinic.name, + phone: clinic.phone, + canPayOnline: clinic.canPayOnline, + opening_at: clinic.opening_at, + closing_at: clinic.closing_at, + address: clinic.address, + address_maps_link: clinic.address_maps_link || "", + doctors: allDoctors + }); + } + + return clinicsData; + + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index a520ea4..7fdc32a 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -264,29 +264,6 @@ export class DoctorService { throw error; } } - public async getOnlineDoctors(): Promise[]> { - const doctors = await prisma.doctor.findMany({ - where: { - account_status: 'APPROVED', - present: true, - availability_type: { - in: ['ONLINE', 'BOTH'] - } - }, - select: { - id: true, - user: { - select: { - name: true, - } - } - } - }); - return doctors.map(doctor => ({ - id: doctor.id, - name: doctor.user.name, - })); - } public async getDoctors(gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean): Promise { const WhereClause: any = { diff --git a/src/swagger-output.json b/src/swagger-output.json index c5b433b..5666c5e 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3801,19 +3801,19 @@ "tags": [ "Appointments" ], - "description": "Get all clinics available for booking appointments", + "description": "Get all active clinics available for booking appointments", "parameters": [ { - "name": "canPayOnline", + "name": "payOnline", "in": "query", - "description": "Filter clinics by online payment availability", + "description": "Filter clinics that support online payment (true) or not (false)", "required": false, "type": "boolean" } ], "responses": { "200": { - "description": "Active clinics retrieved successfully", + "description": "Clinics retrieved successfully", "schema": { "type": "object", "properties": { @@ -3830,9 +3830,17 @@ "type": "string", "example": "New Cairo Medical Clinic" }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "canPayOnline": { + "type": "boolean", + "example": true + }, "opening_at": { "type": "string", - "example": "10:00" + "example": "09:00" }, "closing_at": { "type": "string", @@ -3846,26 +3854,61 @@ "type": "string", "example": "https://maps.google.com/?q=123+Main+Street" }, - "phone": { - "type": "string", - "example": "+1234567890" - }, - "canPayOnline": { - "type": "boolean", - "example": true + "doctors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "doctor-uuid2" + }, + "name": { + "type": "string", + "example": "House" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "age": { + "type": "number", + "example": 45 + }, + "phone": { + "type": "string", + "example": "+1234567890" + }, + "fees": { + "type": "number", + "example": 200 + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" + } + } + } } } } }, - "message": { + "messageEn": { "type": "string", "example": "Clinics retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع العيادات بنجاح" } }, "xml": { "name": "main" } } + }, + "400": { + "description": "Bad request" } } } From e57c94f61c9bb5b5aa7c3f846e1df52ea4ea6143 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 10 Feb 2026 02:45:03 +0200 Subject: [PATCH 137/210] handle both languages in doctor's specialization --- src/controllers/clinic.controller.ts | 9 ++++-- src/controllers/doctor.controller.ts | 9 ++++-- src/routes/appointment.route.ts | 13 +++++++++ src/services/clinic.service.ts | 31 +++++---------------- src/services/doctor.service.ts | 41 ++++++++++++++++------------ src/swagger-output.json | 14 ++++++++++ src/utils/errorMessages.ts | 8 ++++-- 7 files changed, 78 insertions(+), 47 deletions(-) diff --git a/src/controllers/clinic.controller.ts b/src/controllers/clinic.controller.ts index 016302f..f4620d3 100644 --- a/src/controllers/clinic.controller.ts +++ b/src/controllers/clinic.controller.ts @@ -114,9 +114,14 @@ export class ClinicController { } public getActiveClinics = async (req: Request, res: Response, next: NextFunction): Promise => { - const { canPayOnline } = req.query; + const { canPayOnline, lang } = req.query; + if (!lang || (lang !== 'en' && lang !== 'ar')) { + const error = createBilingualError(400, ErrorMessages.SPECIALIZATION_LANG); + throw new HttpException(400, error.message, error.messageAr); + } + const payOnline = canPayOnline !== undefined ? canPayOnline === 'true' : undefined; - const clinics = await this.clinicService.getClinics(payOnline); + const clinics = await this.clinicService.getActiveClinics(lang as 'en' | 'ar', payOnline); const response = createMultiLangMessage(SuccessResponseMessages.CLINICS_RETRIEVED_SUCCESSFULLY); res.status(200).json({ data: clinics, diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index ef9506c..444bf0b 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -50,7 +50,12 @@ export class DoctorController { } public getDoctors = async (req: Request, res: Response, next: NextFunction): Promise => { - const { gender, minFees, maxFees, isOnline } = req.query; + const { gender, minFees, maxFees, isOnline, lang } = req.query; + + if (!lang || (lang !== 'en' && lang !== 'ar')) { + const error = createBilingualError(400, ErrorMessages.SPECIALIZATION_LANG); + throw new HttpException(400, error.message, error.messageAr); + } const finalIsOnline = isOnline !== undefined ? isOnline === 'true' : undefined; const finalGender = gender as string | undefined; @@ -63,7 +68,7 @@ export class DoctorController { throw new HttpException(error.status, error.message, error.messageAr); } - const doctors = await this.doctorService.getDoctors(finalGender, finalMinFees, finalMaxFees, finalIsOnline); + const doctors = await this.doctorService.getDoctors(lang as 'en' | 'ar', finalGender, finalMinFees, finalMaxFees, finalIsOnline); const response = createMultiLangMessage(SuccessResponseMessages.DOCTORS_RETRIEVED_SUCCESSFULLY); res.status(200).json({ diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index a2e4fe2..b399ddf 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -26,6 +26,12 @@ export class AppointmentRoute implements Routes { #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.description = 'Get all doctors available for booking appointments' + #swagger.parameters['lang'] = { + in: 'query', + description: 'Required language for specialization', + required: true, + type: 'string' + } #swagger.parameters['gender'] = { in: 'query', description: 'Filter doctors by gender (MALE or FEMALE)', @@ -96,6 +102,12 @@ export class AppointmentRoute implements Routes { #swagger.method = 'get' #swagger.tags = ['Appointments'] #swagger.description = 'Get all active clinics available for booking appointments' + #swagger.parameters['lang'] = { + in: 'query', + description: 'Required language for specialization', + required: true, + type: 'string' + } #swagger.parameters['payOnline'] = { in: 'query', description: 'Filter clinics that support online payment (true) or not (false)', @@ -121,6 +133,7 @@ export class AppointmentRoute implements Routes { name: 'John Doe', gender: 'MALE', age: 45, + specialization: 'IMMUNOLOGY', phone: '+1234567890', fees: 200, profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg' diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 64b34fe..85bc11f 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -9,6 +9,8 @@ import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { HttpException } from "@/exceptions/HttpException"; import { UserService } from "./user.service"; import { DoctorAccountStatus } from "@prisma/client"; +import { formatSpecializationResponse } from "@/utils/specializationTransform"; +import { SpecializationKey } from "@/constants/specializations"; @Service() export class ClinicService { @@ -275,29 +277,6 @@ export class ClinicService { return results; } - // public async getActiveClinics(payOnline?: boolean): Promise[]> { - // const clinics = await prisma.clinic.findMany({ - // where: { - // is_active: true, - // deleted_at: null, - // ...(payOnline !== undefined && { canPayOnline: payOnline }), - // }, - // select: { - // id: true, - // name: true, - // opening_at: true, - // closing_at: true, - // address: true, - // address_maps_link: true, - // phone: true, - // canPayOnline: true, - // } - // }); - // return clinics.map(c => ({ - // ...c - // })); - // } - public async getAllClinics(): Promise { const clinics = await prisma.clinic.findMany({ select: { @@ -368,7 +347,7 @@ export class ClinicService { return true; } - public async getClinics(payOnline?: boolean): Promise { + public async getActiveClinics(lang: 'en' | 'ar', payOnline?: boolean): Promise { const clinicDoctors = await prisma.clinicDoctor.findMany({ where: { @@ -435,11 +414,15 @@ export class ClinicService { for (const record of doctorRecords) { const age = await this.userService.calculateUserAge(record.doctor.user.date_of_birth); + const specResponse = formatSpecializationResponse(record.doctor.specialization as SpecializationKey, lang); + const specialization = specResponse.value; + allDoctors.push({ id: record.doctor.user.id, name: record.doctor.user.name, gender: record.doctor.user.gender, age, + specialization, phone: record.doctor.user.phone, fees: representativeRecord.fees, profilePic: record.doctor.user.photo_url, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 7fdc32a..5d895d4 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -13,7 +13,8 @@ import fs from "fs"; import { AvailabilityType, Gender } from "@prisma/client"; import { UserService } from "./user.service"; import { DoctorClinics } from "@/interfaces"; - +import { formatSpecializationResponse } from "@/utils/specializationTransform"; +import { SpecializationKey } from "@/constants/specializations"; const authService = new AuthService(); @@ -265,9 +266,9 @@ export class DoctorService { } } - public async getDoctors(gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean): Promise { + public async getDoctors(lang: 'en' | 'ar', gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean ): Promise { const WhereClause: any = { - is_accepting: true, + is_accepting: true, doctor: { account_status: DoctorAccountStatus.APPROVED, } @@ -319,7 +320,7 @@ export class DoctorService { }, }, }, - clinic: { + clinic: { select: { id: true, name: true, @@ -357,27 +358,33 @@ export class DoctorService { const age = await this.userService.calculateUserAge(user.date_of_birth); const allClinics: DoctorClinics[] = []; - if (!isOnline){ + if (!isOnline) { for (const record of clinicRecords) { - allClinics.push({ - id: record.clinic.id, - name: record.clinic.name, - phone: record.clinic.phone, - canPayOnline: record.clinic.canPayOnline, - opening_at: record.clinic.opening_at, - closing_at: record.clinic.closing_at, - address: record.clinic.address, - address_maps_link: record.clinic.address_maps_link || "", - }); - } + allClinics.push({ + id: record.clinic.id, + name: record.clinic.name, + phone: record.clinic.phone, + canPayOnline: record.clinic.canPayOnline, + opening_at: record.clinic.opening_at, + closing_at: record.clinic.closing_at, + address: record.clinic.address, + address_maps_link: record.clinic.address_maps_link || "", + }); + } } + const specResponse = formatSpecializationResponse( + doctor.specialization as SpecializationKey, + lang + ); + + const specialization = specResponse.value; doctorPersonalData.push({ id: user.id, name: user.name, gender: user.gender, age, - specialization: doctor.specialization, + specialization, phone: user.phone, fees: representativeRecord.fees, profilePic: user.photo_url, diff --git a/src/swagger-output.json b/src/swagger-output.json index 5666c5e..acf84a3 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3661,6 +3661,13 @@ ], "description": "Get all doctors available for booking appointments", "parameters": [ + { + "name": "lang", + "in": "query", + "description": "Required language for specialization", + "required": true, + "type": "string" + }, { "name": "gender", "in": "query", @@ -3803,6 +3810,13 @@ ], "description": "Get all active clinics available for booking appointments", "parameters": [ + { + "name": "lang", + "in": "query", + "description": "Required language for specialization", + "required": true, + "type": "string" + }, { "name": "payOnline", "in": "query", diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 8bc72b9..cfb077e 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -148,6 +148,10 @@ export const ErrorMessages = { en: 'Schedule already exists for this day and clinic', ar: 'الجدول موجود بالفعل لهذا اليوم والعيادة' }, + SPECIALIZATION_LANG: { + en: "Language must be 'en' or 'ar'", + ar: "يجب أن تكون اللغة 'en' أو 'ar'" + }, // Clinic errors CLINIC_NOT_FOUND: { @@ -248,8 +252,8 @@ export const ErrorMessages = { ar: "يوجد تعارض بين المواعيد الإلكترونية والحضورية" }, EITHER_ONLINE_OR_OFFLINE: { - en: "Please choose either online or offline", - ar: "يرجى اختيار إما الإلكتروني أو الحضوري" + en: "Please choose either online or offline", + ar: "يرجى اختيار إما الإلكتروني أو الحضوري" }, // Generic errors SOMETHING_WENT_WRONG: { From 3fc902a43ae635e94aad0b50ee8d2ba1661c6676 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 10 Feb 2026 16:40:02 +0200 Subject: [PATCH 138/210] added check for doctor account status upon login --- src/services/auth.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 918a811..c502251 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { Role } from '@prisma/client'; +import { DoctorAccountStatus, Role } from '@prisma/client'; import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; @@ -93,6 +93,10 @@ export class AuthService { account_status: doctor.account_status } : undefined }; + if( patientLoginData.doctor && patientLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); From c1e6fd2b3fe7539156b1cb4716483dbe7395c692 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 10 Feb 2026 19:03:52 +0200 Subject: [PATCH 139/210] fixed doctor login endpoint --- src/services/auth.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index c502251..934e5f9 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -64,7 +64,8 @@ export class AuthService { { email: userData.emailOrUsername }, { username: userData.emailOrUsername } ] - } + }, + include: { doctor: true } }); if (!findUser) { const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); @@ -92,7 +93,7 @@ export class AuthService { specialization: doctor.specialization, account_status: doctor.account_status } : undefined - }; + }; if( patientLoginData.doctor && patientLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); throw new HttpException(error.status, error.message, error.messageAr); From c72c001899ffa08bd38ab82c9f0f188b93c308c7 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 10 Feb 2026 22:05:34 +0200 Subject: [PATCH 140/210] fix: swagger / appointments --- src/routes/appointment.route.ts | 4 ++-- src/services/appointment.service.ts | 17 +++++++++++++++++ src/swagger-output.json | 2 +- src/utils/errorMessages.ts | 5 +++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index b399ddf..e48c6b5 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -108,7 +108,7 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } - #swagger.parameters['payOnline'] = { + #swagger.parameters['canPayOnline'] = { in: 'query', description: 'Filter clinics that support online payment (true) or not (false)', required: false, @@ -1076,7 +1076,7 @@ export class AppointmentRoute implements Routes { } */ AuthMiddleware, - this.appointmentController.checkConflictingAppointments + this.appointmentController.checkConflictingAppointments ); this.router.patch( diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 10dc94f..e7d9074 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -21,6 +21,7 @@ export class AppointmentService { const schedules = await prisma.doctorSchedule.findMany({ where: { doctor_id: doctorId, + clinic_id: clinicId, deleted_at: null }, select: { @@ -127,6 +128,7 @@ export class AppointmentService { where: { day_of_week: dayOfWeek, doctor_id: doctorId, + clinic_id: clinicId, deleted_at: null, }, select: { @@ -214,6 +216,21 @@ export class AppointmentService { } public async bookAppointment(patientId: string, doctorId: string, clinicId: string | null, scheduledTime: Date): Promise { + const existingAppointment = await prisma.appointment.findFirst({ + where: { + patient_id: patientId, + doctor_id: doctorId, + clinic_id: clinicId, + scheduled_time: scheduledTime, + deleted_at: null, + } + }) + + if (existingAppointment) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ALREADY_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + const schedule = await prisma.doctorSchedule.findFirst({ where: { doctor_id: doctorId, diff --git a/src/swagger-output.json b/src/swagger-output.json index acf84a3..f3efaa9 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3818,7 +3818,7 @@ "type": "string" }, { - "name": "payOnline", + "name": "canPayOnline", "in": "query", "description": "Filter clinics that support online payment (true) or not (false)", "required": false, diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index cfb077e..bbba58b 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -103,6 +103,7 @@ export const ErrorMessages = { en: 'Maximum number of created clinics reached', ar: 'تم الوصول إلى الحد الأقصى لعدد العيادات', }, + // File upload errors NO_FILE_UPLOADED: { en: 'No file uploaded', @@ -195,6 +196,10 @@ export const ErrorMessages = { en: 'Invalid fees range.', ar: 'نطاق الرسوم غير صالح.' }, + APPOINTMENT_ALREADY_EXISTS: { + en: 'Appointment already exists.', + ar: 'الموعد موجود بالفعل.' + }, PATIENT_ID_REQUIRED: { en: 'Patient ID is required', ar: 'معرف المريض مطلوب', From 18c2ee56d91a0a9fedb63ba478cdd70044e3b2c8 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 10 Feb 2026 23:05:29 +0200 Subject: [PATCH 141/210] return availability type in doctors response --- src/interfaces/doctors.interface.ts | 1 + src/routes/appointment.route.ts | 2 ++ src/services/clinic.service.ts | 11 +++++++++-- src/services/doctor.service.ts | 15 ++++++++++----- src/swagger-output.json | 4 ++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts index 3c1fd62..d11a52c 100644 --- a/src/interfaces/doctors.interface.ts +++ b/src/interfaces/doctors.interface.ts @@ -32,5 +32,6 @@ export interface DoctorPersonalData { phone: string; fees: number; profilePic: string; + is_online: boolean; clinics?: DoctorClinics[] } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e48c6b5..059e0b0 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -68,6 +68,7 @@ export class AppointmentRoute implements Routes { specialization: 'IMMUNOLOGY', phone: '+1234567890', fees: 200, + is_online: true, profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', clinics: [ { @@ -136,6 +137,7 @@ export class AppointmentRoute implements Routes { specialization: 'IMMUNOLOGY', phone: '+1234567890', fees: 200, + is_online: true, profilePic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg' }, { diff --git a/src/services/clinic.service.ts b/src/services/clinic.service.ts index 85bc11f..3061ca5 100644 --- a/src/services/clinic.service.ts +++ b/src/services/clinic.service.ts @@ -361,7 +361,9 @@ export class ClinicService { }, include: { doctor: { - include: { + select: { + availability_type: true, + specialization: true, user: { select: { id: true, @@ -414,9 +416,13 @@ export class ClinicService { for (const record of doctorRecords) { const age = await this.userService.calculateUserAge(record.doctor.user.date_of_birth); + let isOnline = false; + if (record.doctor.availability_type == 'ONLINE' || record.doctor.availability_type == 'BOTH') { + isOnline = true; + } const specResponse = formatSpecializationResponse(record.doctor.specialization as SpecializationKey, lang); const specialization = specResponse.value; - + allDoctors.push({ id: record.doctor.user.id, name: record.doctor.user.name, @@ -425,6 +431,7 @@ export class ClinicService { specialization, phone: record.doctor.user.phone, fees: representativeRecord.fees, + is_online: isOnline, profilePic: record.doctor.user.photo_url, }); } diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 5d895d4..843614a 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -307,7 +307,9 @@ export class DoctorService { where: WhereClause, include: { doctor: { - include: { + select: { + availability_type: true, + specialization: true, user: { select: { id: true, @@ -316,6 +318,7 @@ export class DoctorService { phone: true, date_of_birth: true, photo_url: true, + }, }, }, @@ -356,6 +359,10 @@ export class DoctorService { if (!user) continue; const age = await this.userService.calculateUserAge(user.date_of_birth); + let canWorkOnline = false; + if (doctor.availability_type == 'ONLINE' || doctor.availability_type == 'BOTH') { + canWorkOnline = true; + } const allClinics: DoctorClinics[] = []; if (!isOnline) { @@ -372,10 +379,7 @@ export class DoctorService { }); } } - const specResponse = formatSpecializationResponse( - doctor.specialization as SpecializationKey, - lang - ); + const specResponse = formatSpecializationResponse(doctor.specialization as SpecializationKey, lang); const specialization = specResponse.value; @@ -388,6 +392,7 @@ export class DoctorService { phone: user.phone, fees: representativeRecord.fees, profilePic: user.photo_url, + is_online: canWorkOnline, clinics: allClinics, }); } diff --git a/src/swagger-output.json b/src/swagger-output.json index f3efaa9..fb063ac 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3736,6 +3736,10 @@ "type": "number", "example": 200 }, + "is_online": { + "type": "boolean", + "example": true + }, "profilePic": { "type": "string", "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" From 5a428a217fe3914284ad5735aa6abec106fbfe2e Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 11 Feb 2026 22:47:17 +0200 Subject: [PATCH 142/210] feat: update user profile and doctor avail type endpoint --- src/controllers/user.controller.ts | 12 +++++++++ src/dtos/doctors.dto.ts | 8 +++++- src/dtos/users.dto.ts | 13 ++++++++- src/routes/user.route.ts | 43 +++++++++++++++++++++++++++++- src/services/user.service.ts | 35 +++++++++++++++++++++++- src/utils/errorMessages.ts | 5 +++- src/utils/responseMessages.ts | 4 +++ 7 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 6bdcb6b..3c3ad84 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -41,4 +41,16 @@ export class UsersController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.PROFILE_PICTURE_DELETED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } + + public updateUserProfile = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + const { name, phone, gender, dateOfBirth, availability_type } = req.body; + if(!name && !phone && !gender && !dateOfBirth) { + const error = createBilingualError(400, ErrorMessages.NO_PROFILE_DATA_PROVIDED); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.userService.updateUserProfile(userId, name, phone, gender, dateOfBirth, availability_type); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.USER_PROFILE_UPDATED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index 50b2300..eac66ff 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -1,7 +1,8 @@ import { TransformSpecialization } from "@/utils/specializationTransform"; import { IsValidSpecialization } from "@/validators/specialization.validator"; -import { Gender } from "@prisma/client"; +import { AvailabilityType, Gender } from "@prisma/client"; import { IsString, IsNotEmpty, IsEmail } from "class-validator"; +import { UpdateUserProfileDto } from "./users.dto"; export class DoctorSignupRequestDto { @IsString() @@ -58,4 +59,9 @@ export class DoctorSetPasswordRequestDto { export class DoctorProfilePictureRequestDto { profilePicture: Express.Multer.File; +} + +export class DoctorUpdateProfileRequestDto extends UpdateUserProfileDto { + @IsString() + availability_type?: AvailabilityType; } \ No newline at end of file diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index 9a0f4c1..c91dcec 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -65,4 +65,15 @@ export class ResetPasswordDto { @MinLength(8) @MaxLength(32) public newPassword: string; -} \ No newline at end of file +} + +export class UpdateUserProfileDto { + @IsString() + public name?: string; + @IsString() + public phone?: string; + @IsString() + public gender?: Gender; + @IsDateString() + public date_of_birth?: Date; +} diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 3fc66f5..d1ffc77 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -1,4 +1,5 @@ import { UsersController } from "@/controllers/user.controller"; +import { UpdateUserProfileDto } from "@/dtos/users.dto"; import { Routes } from "@/interfaces"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; import {uploadImage} from "@/middlewares/multer.middleware"; @@ -94,5 +95,45 @@ export class UsersRoute implements Routes { AuthMiddleware, errorWrapper(this.usersController.deleteProfilePicture) ); + + this.router.patch( + `${this.path}/update-profile`, + /* + #swagger.path = '/users/update-profile' + #swagger.tags = ['Users'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: 'object', + properties: { + name: { type: 'string', example: 'John Doe' }, + phone: { type: 'string', example: '+1234567890' }, + gender: { type: 'enum', enum: ['MALE', 'FEMALE'], example: 'MALE' }, + dateOfBirth: { type: 'string', format: 'date', example: '1990-01-01' } + } + } + } + } + } + #swagger.responses[200] = { + description: 'Profile updated successfully', + schema: { + messageEn: 'Profile updated successfully', + messageAr: "تم تحديث الملف الشخصي بنجاح" + } + } + */ + AuthMiddleware, + ValidationMiddleware(UpdateUserProfileDto, false, false, true), + errorWrapper(this.usersController.updateUserProfile) + ); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/services/user.service.ts b/src/services/user.service.ts index 6202a78..bd82e5c 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -1,9 +1,10 @@ import cloudinary from "@/utils/cloudinary"; -import { PrismaClient } from "@prisma/client"; +import { AvailabilityType, Gender, PrismaClient, Role } from "@prisma/client"; import { Service } from "typedi"; import fs from "fs"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { HttpException } from "@/exceptions/HttpException"; +import { UpdateUserProfileDto } from "@/dtos/users.dto"; const prisma = new PrismaClient(); @@ -86,4 +87,36 @@ export class UserService { } return age; } + public async updateUserProfile(userId: string, name?: string, phone?: string, gender?: Gender, dateOfBirth?: string, availability_type?: AvailabilityType): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + }); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const updateData: UpdateUserProfileDto = {}; + if (name) updateData.name = name; + if (phone) updateData.phone = phone; + if (gender) updateData.gender = gender; + if (dateOfBirth) updateData.date_of_birth = new Date(dateOfBirth); + if(availability_type && user.role === Role.DOCTOR) { + await prisma.$transaction([ + prisma.user.update({ + where: { id: userId }, + data: updateData + }), + prisma.doctor.update({ + where: { id: userId }, + data: { availability_type } + }) + ]); + } + else { + await prisma.user.update({ + where: { id: userId }, + data: updateData + }); + } + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index bbba58b..e35a755 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -79,7 +79,10 @@ export const ErrorMessages = { en: 'Validation error', ar: 'خطأ في التحقق من البيانات', }, - + NO_PROFILE_DATA_PROVIDED: { + en: 'No profile data provided for update', + ar: 'لم يتم تقديم بيانات الملف الشخصي للتحديث', + }, // Google Auth errors NO_EMAIL_IN_GOOGLE_PROFILE: { en: 'No email found in Google profile', diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index ea99ceb..7f770a7 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -140,6 +140,10 @@ export const SuccessResponseMessages = { message_en: "Profile picture deleted successfully.", message_ar: "تم حذف صورة الملف الشخصي بنجاح.", }, + USER_PROFILE_UPDATED_SUCCESSFULLY: { + message_en: "User profile updated successfully.", + message_ar: "تم تحديث الملف الشخصي للمستخدم بنجاح.", + }, // success messages for appointments APPOINTMENT_BOOKED_SUCCESSFULLY: { message_en: "Appointment booked successfully.", From 4719e198ea383d05315883d147c55ec63fd10255 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Wed, 11 Feb 2026 23:19:25 +0200 Subject: [PATCH 143/210] feat: change password endpoint --- src/controllers/auth.controller.ts | 18 +- src/controllers/user.controller.ts | 3 +- src/dtos/users.dto.ts | 12 + src/routes/auth.route.ts | 707 ++++++++++++++++------------- src/routes/user.route.ts | 28 +- src/services/auth.service.ts | 31 +- src/services/user.service.ts | 1 + src/swagger-output.json | 185 ++++++++ src/utils/responseMessages.ts | 8 + 9 files changed, 656 insertions(+), 337 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 6ab33d8..7e96069 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -3,7 +3,7 @@ import { Container } from 'typedi'; import { RequestWithUser } from '@interfaces/auth.interface'; import { User } from '@interfaces/users.interface'; import { AuthService } from '@services/auth.service'; -import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@/dtos/users.dto'; +import { ChangePasswordDto, CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@/dtos/users.dto'; import { catchAsync } from '@/utils/catchAsync'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { HttpException } from '@/exceptions/HttpException'; @@ -139,5 +139,21 @@ export class AuthController { messageAr: responseMessage.messageAr }); }); + + public checkPassword = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + const { password } = req.body; + const isMatch = await this.auth.checkPassword(userId, password); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_CHECK_SUCCESSFUL); + res.status(200).json({ data: { isMatch }, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } + + public changePassword = async (req: RequestWithUser, res: Response, next: NextFunction) => { + const userId = req.user?.id; + const { newPassword }: ChangePasswordDto = req.body; + await this.auth.changePassword(userId, newPassword); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_CHANGED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 3c3ad84..1bc1c87 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -45,7 +45,7 @@ export class UsersController { public updateUserProfile = async (req: RequestWithUser, res: Response, next: NextFunction) => { const userId = req.user?.id; const { name, phone, gender, dateOfBirth, availability_type } = req.body; - if(!name && !phone && !gender && !dateOfBirth) { + if (!name && !phone && !gender && !dateOfBirth) { const error = createBilingualError(400, ErrorMessages.NO_PROFILE_DATA_PROVIDED); throw new HttpException(error.status, error.message, error.messageAr); } @@ -53,4 +53,5 @@ export class UsersController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.USER_PROFILE_UPDATED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } + } \ No newline at end of file diff --git a/src/dtos/users.dto.ts b/src/dtos/users.dto.ts index c91dcec..bd33d9e 100644 --- a/src/dtos/users.dto.ts +++ b/src/dtos/users.dto.ts @@ -77,3 +77,15 @@ export class UpdateUserProfileDto { @IsDateString() public date_of_birth?: Date; } + +export class PasswordCheckDto { + @IsString() + @IsNotEmpty() + public password: string; +} + +export class ChangePasswordDto { + @IsString() + @IsNotEmpty() + public newPassword: string; +} \ No newline at end of file diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 73254bb..9d515b4 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -1,342 +1,415 @@ import { Router } from 'express'; import { AuthController } from '@controllers/auth.controller'; -import { CompleteUserProfileDto, CreateUserDto, LoginUserDto, ResetPasswordDto } from '@dtos/users.dto'; +import { ChangePasswordDto, CompleteUserProfileDto, CreateUserDto, LoginUserDto, PasswordCheckDto, ResetPasswordDto } from '@dtos/users.dto'; import { Routes } from '@interfaces/routes.interface'; import { AuthMiddleware } from '@middlewares/auth.middleware'; import { GoogleAuthController } from '@/controllers/googleAuth.controller'; import { ValidationMiddleware } from '@middlewares/validation.middleware'; import { UpdateGoogleUserPhoneDto } from '@/dtos/googleUsers.dto'; +import { errorWrapper } from '@/utils/errorWrapper'; export class AuthRoute implements Routes { - public path = '/auth'; - public router = Router(); - public auth = new AuthController(); - public googleAuth = new GoogleAuthController(); + public path = '/auth'; + public router = Router(); + public auth = new AuthController(); + public googleAuth = new GoogleAuthController(); - constructor() { - this.initializeRoutes(); - } + constructor() { + this.initializeRoutes(); + } - private initializeRoutes() { - this.router.post( - `/auth/signup`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'User signup data', - required: true, - schema: { - $email: 'user@example.com', - $name: 'John Doe', - $phone: '1234567890', - $password: 'password123', - $rememberMe: false - } - } - #swagger.responses[201] = { - description: 'User successfully created', - schema: { - data: { - id: 1, - email: 'user@example.com', - name: 'John Doe', - phone: '1234567890', - isEmailVerified: false, - hasCompletedProfile: false, - gender: null, - date_of_birth: null, - role: 'PATIENT', - photoUrl: null - }, - messageEn: 'Signed Up Successfully', - messageAr: "تم انشاء الحساب بنجاح" - } - } - */ - ValidationMiddleware(CreateUserDto), - this.auth.signUp, - ); + private initializeRoutes() { + this.router.post( + `/auth/signup`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User signup data', + required: true, + schema: { + $email: 'user@example.com', + $name: 'John Doe', + $phone: '1234567890', + $password: 'password123', + $rememberMe: false + } + } + #swagger.responses[201] = { + description: 'User successfully created', + schema: { + data: { + id: 1, + email: 'user@example.com', + name: 'John Doe', + phone: '1234567890', + isEmailVerified: false, + hasCompletedProfile: false, + gender: null, + date_of_birth: null, + role: 'PATIENT', + photoUrl: null + }, + messageEn: 'Signed Up Successfully', + messageAr: "تم انشاء الحساب بنجاح" + } + } + */ + ValidationMiddleware(CreateUserDto), + this.auth.signUp, + ); - this.router.post( - `/auth/login`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'User login data', - required: true, - schema: { - $emailOrUsername: 'user@example.com', - $password: 'password123', - rememberMe: false - } - } - #swagger.responses[200] = { - description: 'Login successful', - schema: { - data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT' , doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, - messageEn: 'Logged In Successfully', - messageAr: "تم تسجيل الدخول بنجاح" - } - } - */ - ValidationMiddleware(LoginUserDto), - this.auth.logIn, - ); + this.router.post( + `/auth/login`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'User login data', + required: true, + schema: { + $emailOrUsername: 'user@example.com', + $password: 'password123', + rememberMe: false + } + } + #swagger.responses[200] = { + description: 'Login successful', + schema: { + data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT' , doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, + messageEn: 'Logged In Successfully', + messageAr: "تم تسجيل الدخول بنجاح" + } + } + */ + ValidationMiddleware(LoginUserDto), + this.auth.logIn, + ); - this.router.post( - `/auth/logout`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cookie)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'Logout successful', - schema: { messageEn: 'Logged Out Successfully', messageAr: "تم تسجيل الخروج بنجاح" } - } - */ - AuthMiddleware, - this.auth.logOut, - ); + this.router.post( + `/auth/logout`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Logout successful', + schema: { messageEn: 'Logged Out Successfully', messageAr: "تم تسجيل الخروج بنجاح" } + } + */ + AuthMiddleware, + this.auth.logOut, + ); - this.router.post( - `/auth/refresh`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['RefreshToken'] = { - in: 'header', - description: 'Refresh token (sent via RefreshToken cookie)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'Token refreshed successfully', - schema: { - data: { user: {}, accessToken: { expiresIn: 3600, expiresAt: '2025-12-12T12:00:00.000Z' } }, - messageEn: 'Token Refreshed Successfully', - messageAr: "تم تحديث الرمز بنجاح" - } - } - */ - this.auth.refresh, - ); + this.router.post( + `/auth/refresh`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['RefreshToken'] = { + in: 'header', + description: 'Refresh token (sent via RefreshToken cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Token refreshed successfully', + schema: { + data: { user: {}, accessToken: { expiresIn: 3600, expiresAt: '2025-12-12T12:00:00.000Z' } }, + messageEn: 'Token Refreshed Successfully', + messageAr: "تم تحديث الرمز بنجاح" + } + } + */ + this.auth.refresh, + ); - this.router.patch( - `/auth/complete-profile-info`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Complete user profile', - required: true, - schema: { - $gender: 'Male', - $date_of_birth: '1990-01-01' - } - } - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cookie)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'Profile completed successfully', - schema: { - data: { id: 1, hasCompletedProfile: true }, - messageEn: 'Profile Completed Successfully', - messageAr: "تم إكمال الملف الشخصي بنجاح" - } - } - */ - ValidationMiddleware(CompleteUserProfileDto), - /* - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token', - required: true, - type: 'string' - } - */ - AuthMiddleware, - this.auth.completeProfile, - ); + this.router.patch( + `/auth/complete-profile-info`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Complete user profile', + required: true, + schema: { + $gender: 'Male', + $date_of_birth: '1990-01-01' + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Profile completed successfully', + schema: { + data: { id: 1, hasCompletedProfile: true }, + messageEn: 'Profile Completed Successfully', + messageAr: "تم إكمال الملف الشخصي بنجاح" + } + } + */ + ValidationMiddleware(CompleteUserProfileDto), + /* + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ + AuthMiddleware, + this.auth.completeProfile, + ); - this.router.patch( - `/auth/verify-otp`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Verify OTP', - required: true, - schema: { $otp: '123456' } - } - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cookie or)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'OTP verified successfully', - schema: { - data: true, - messageEn: 'OTP Verified Successfully', - messageAr: "تم التحقق من رمز التحقق بنجاح" - } - } - */ - AuthMiddleware, - this.auth.verifyOTP, - ); + this.router.patch( + `/auth/verify-otp`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Verify OTP', + required: true, + schema: { $otp: '123456' } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'OTP verified successfully', + schema: { + data: true, + messageEn: 'OTP Verified Successfully', + messageAr: "تم التحقق من رمز التحقق بنجاح" + } + } + */ + AuthMiddleware, + this.auth.verifyOTP, + ); - this.router.post( - `/auth/forget-password`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Request password reset', - required: true, - schema: { $email: 'user@example.com' } - } - #swagger.responses[200] = { - description: 'Password reset email sent', - schema: { messageEn: 'Password Reset Email Sent Successfully', messageAr: "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح" } - } - */ - this.auth.forgetPassword, - ); + this.router.post( + `/auth/forget-password`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Request password reset', + required: true, + schema: { $email: 'user@example.com' } + } + #swagger.responses[200] = { + description: 'Password reset email sent', + schema: { messageEn: 'Password Reset Email Sent Successfully', messageAr: "تم إرسال بريد إعادة تعيين كلمة المرور بنجاح" } + } + */ + this.auth.forgetPassword, + ); - this.router.post( - `/auth/reset-password`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Reset password', - required: true, - schema: { - $token: 'reset-token', - $newPassword: 'newPassword123' - } - } - #swagger.responses[200] = { - description: 'Password reset successfully', - schema: { messageEn: 'Password Reset Successfully', messageAr: "تم إعادة تعيين كلمة المرور بنجاح" } - } - */ - ValidationMiddleware(ResetPasswordDto), - this.auth.resetPassword, - ); + this.router.post( + `/auth/reset-password`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Reset password', + required: true, + schema: { + $token: 'reset-token', + $newPassword: 'newPassword123' + } + } + #swagger.responses[200] = { + description: 'Password reset successfully', + schema: { messageEn: 'Password Reset Successfully', messageAr: "تم إعادة تعيين كلمة المرور بنجاح" } + } + */ + ValidationMiddleware(ResetPasswordDto), + this.auth.resetPassword, + ); - this.router.post( - `/auth/resend-otp`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cooki)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'OTP resent successfully', - schema: { messageEn: 'OTP Resent Successfully', messageAr: "تم إعادة إرسال رمز التحقق بنجاح" } - } - */ - AuthMiddleware, - this.auth.resendOTP, - ); + this.router.post( + `/auth/resend-otp`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cooki)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'OTP resent successfully', + schema: { messageEn: 'OTP Resent Successfully', messageAr: "تم إعادة إرسال رمز التحقق بنجاح" } + } + */ + AuthMiddleware, + this.auth.resendOTP, + ); - this.router.get( - `/auth/google`, - /* - #swagger.tags = ['Auth'] - #swagger.responses[302] = { - description: 'Redirects to Google OAuth consent page' - } - */ - this.googleAuth.googleOAuth, - ); + this.router.get( + `/auth/google`, + /* + #swagger.tags = ['Auth'] + #swagger.responses[302] = { + description: 'Redirects to Google OAuth consent page' + } + */ + this.googleAuth.googleOAuth, + ); - this.router.get( - `/auth/google/callback`, - /* - #swagger.tags = ['Auth'] - #swagger.responses[302] = { - description: 'Redirects after Google authentication' - } - */ - this.googleAuth.googleOAuthCallback, - ); + this.router.get( + `/auth/google/callback`, + /* + #swagger.tags = ['Auth'] + #swagger.responses[302] = { + description: 'Redirects after Google authentication' + } + */ + this.googleAuth.googleOAuthCallback, + ); - this.router.patch( - `/auth/google/update-phone`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['body'] = { - in: 'body', - description: 'Update Google user phone', - required: true, - schema: { $phone: '1234567890' } - } - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cookie or)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'Phone number updated successfully', - schema: { - data: { phone: '1234567890' }, - messageEn: 'Phone number updated successfully', - messageAr: "تم تحديث رقم الهاتف بنجاح" - } - } - */ - ValidationMiddleware(UpdateGoogleUserPhoneDto), - /* - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token', - required: true, - type: 'string' - } - */ - AuthMiddleware, - this.googleAuth.updatePhoneNumber, - ); + this.router.patch( + `/auth/google/update-phone`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Update Google user phone', + required: true, + schema: { $phone: '1234567890' } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Phone number updated successfully', + schema: { + data: { phone: '1234567890' }, + messageEn: 'Phone number updated successfully', + messageAr: "تم تحديث رقم الهاتف بنجاح" + } + } + */ + ValidationMiddleware(UpdateGoogleUserPhoneDto), + /* + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token', + required: true, + type: 'string' + } + */ + AuthMiddleware, + this.googleAuth.updatePhoneNumber, + ); - this.router.get( - `/auth/google/userData`, - /* - #swagger.tags = ['Auth'] - #swagger.parameters['Authorization'] = { - in: 'header', - description: 'Bearer access token (sent via Authorization cookie)', - required: false, - type: 'string' - } - #swagger.responses[200] = { - description: 'User data retrieved successfully', - schema: { - data: { email: 'user@example.com', name: 'John Doe', username: 'johndoe', phone: '1234567890', gender: 'MALE' , date_of_birth: '1990-01-01', isVerified: false, hasCompletedProfile: false }, - messageEn: 'User data retrieved successfully', - messageAr: "تم استرجاع بيانات المستخدم بنجاح" - } - } - */ - AuthMiddleware, - this.googleAuth.getGoogleUserData, - ); - } + this.router.get( + `/auth/google/userData`, + /* + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'User data retrieved successfully', + schema: { + data: { email: 'user@example.com', name: 'John Doe', username: 'johndoe', phone: '1234567890', gender: 'MALE' , date_of_birth: '1990-01-01', isVerified: false, hasCompletedProfile: false }, + messageEn: 'User data retrieved successfully', + messageAr: "تم استرجاع بيانات المستخدم بنجاح" + } + } + */ + AuthMiddleware, + this.googleAuth.getGoogleUserData, + ); + + this.router.post( + `${this.path}/check-password`, + /* + #swagger.path = '/auth/check-password' + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: 'object', + properties: { + password: { type: 'string', example: 'your_password' } + } + } + } + } + } + #swagger.responses[200] = { + description: 'Password check successful', + schema: { + data: { + isMatch: true + }, + messageEn: 'Password is correct', + messageAr: "كلمة المرور صحيحة" + } + } + */ + AuthMiddleware, + ValidationMiddleware(PasswordCheckDto), + errorWrapper(this.auth.checkPassword) + ); + + this.router.patch( + `${this.path}/change-password`, + /* + #swagger.path = '/auth/change-password' + #swagger.tags = ['Auth'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'User password change data', + required: true, + schema: { + newPassword: 'new_password123' + } + } + #swagger.responses[200] = { + description: 'Password changed successfully', + schema: { + messageEn: 'Password changed successfully', + messageAr: "تم تغيير كلمة المرور بنجاح" + } + } + */ + AuthMiddleware, + ValidationMiddleware(ChangePasswordDto), + errorWrapper(this.auth.changePassword) + ); + } } diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index d1ffc77..a12756f 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -1,8 +1,8 @@ import { UsersController } from "@/controllers/user.controller"; -import { UpdateUserProfileDto } from "@/dtos/users.dto"; +import { PasswordCheckDto, UpdateUserProfileDto } from "@/dtos/users.dto"; import { Routes } from "@/interfaces"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; -import {uploadImage} from "@/middlewares/multer.middleware"; +import { uploadImage } from "@/middlewares/multer.middleware"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { errorWrapper } from "@/utils/errorWrapper"; import { Router } from "express"; @@ -107,20 +107,16 @@ export class UsersRoute implements Routes { required: true, type: 'string' } - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: 'object', - properties: { - name: { type: 'string', example: 'John Doe' }, - phone: { type: 'string', example: '+1234567890' }, - gender: { type: 'enum', enum: ['MALE', 'FEMALE'], example: 'MALE' }, - dateOfBirth: { type: 'string', format: 'date', example: '1990-01-01' } - } - } - } + #swagger.parameters['body'] = { + in: 'body', + description: 'User profile update data', + required: false, + schema: { + name: 'John Doe', + phone: '1234567890', + gender: 'MALE or FEMALE', + dateOfBirth: '1990-01-01', + availability_type: 'ONLINE, OFFLINE, BOTH or UNSET' } } #swagger.responses[200] = { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 934e5f9..5f14172 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -93,8 +93,8 @@ export class AuthService { specialization: doctor.specialization, account_status: doctor.account_status } : undefined - }; - if( patientLoginData.doctor && patientLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { + }; + if (patientLoginData.doctor && patientLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); throw new HttpException(error.status, error.message, error.messageAr); } @@ -427,6 +427,33 @@ export class AuthService { }); } + public async checkPassword(userId: string, password: string): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { password_hash: true } + }); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const isMatch = await compare(password, user.password_hash); + return isMatch; + } + + public async changePassword(userId: string, newPassword: string): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + }); + if (!user) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const hashedPassword = await hash(newPassword, 10); + await prisma.user.update({ + where: { id: userId }, + data: { password_hash: hashedPassword } + }); + } // Keep old methods for backward compatibility public createToken(user: User): AccessTokenData { diff --git a/src/services/user.service.ts b/src/services/user.service.ts index bd82e5c..a20566e 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -5,6 +5,7 @@ import fs from "fs"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { HttpException } from "@/exceptions/HttpException"; import { UpdateUserProfileDto } from "@/dtos/users.dto"; +import { compare } from "bcrypt"; const prisma = new PrismaClient(); diff --git a/src/swagger-output.json b/src/swagger-output.json index fb063ac..69d2926 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -785,6 +785,122 @@ } } }, + "/auth/check-password": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Password check successful", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "isMatch": { + "type": "boolean", + "example": true + } + } + }, + "messageEn": { + "type": "string", + "example": "Password is correct" + }, + "messageAr": { + "type": "string", + "example": "كلمة المرور صحيحة" + } + }, + "xml": { + "name": "main" + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "your_password" + } + } + } + } + } + } + } + }, + "/auth/change-password": { + "patch": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "User password change data", + "required": true, + "schema": { + "type": "object", + "properties": { + "newPassword": { + "type": "string", + "example": "new_password123" + } + } + } + } + ], + "responses": { + "200": { + "description": "Password changed successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Password changed successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تغيير كلمة المرور بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/fabric/onboard": { "post": { "tags": [ @@ -5828,6 +5944,75 @@ } } } + }, + "/users/update-profile": { + "patch": { + "tags": [ + "Users" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "User profile update data", + "required": false, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "John Doe" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "dateOfBirth": { + "type": "string", + "example": "1990-01-01" + }, + "availability_type": { + "type": "string", + "example": "ONLINE, OFFLINE, BOTH or UNSET" + } + } + } + } + ], + "responses": { + "200": { + "description": "Profile updated successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Profile updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث الملف الشخصي بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 7f770a7..89d70cb 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -36,6 +36,14 @@ export const SuccessResponseMessages = { message_en: "OTP resent successfully.", message_ar: "تم إعادة إرسال رمز التحقق بنجاح.", }, + PASSWORD_CHECK_SUCCESSFUL: { + message_en: "Password check successful.", + message_ar: "تم التحقق من كلمة المرور بنجاح.", + }, + PASSWORD_CHANGED_SUCCESSFULLY: { + message_en: "Password changed successfully.", + message_ar: "تم تغيير كلمة المرور بنجاح.", + }, // Success messages for Doctors by Admin DOCTOR_CREATED: { From eec9c0dfae2ba128442f3c234c52bb03bb93d568 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 12 Feb 2026 14:13:38 +0200 Subject: [PATCH 144/210] fix: check password swagger --- src/routes/auth.route.ts | 15 +++++---------- src/swagger-output.json | 31 +++++++++++++++---------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 9d515b4..0327790 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -351,17 +351,12 @@ export class AuthRoute implements Routes { required: true, type: 'string' } - #swagger.requestBody = { + #swagger.parameters['body'] = { + in: 'body', + description: 'User current password', required: true, - content: { - "application/json": { - schema: { - type: 'object', - properties: { - password: { type: 'string', example: 'your_password' } - } - } - } + schema: { + password: 'current_password123' } } #swagger.responses[200] = { diff --git a/src/swagger-output.json b/src/swagger-output.json index 69d2926..6946e2a 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -798,6 +798,21 @@ "description": "Bearer token for authentication", "required": true, "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "User current password", + "required": true, + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "current_password123" + } + } + } } ], "responses": { @@ -829,22 +844,6 @@ } } } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "password": { - "type": "string", - "example": "your_password" - } - } - } - } - } } } }, From a95fa7e64fec3b11e7d014f54675367a0e1432de Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 12 Feb 2026 20:35:35 +0200 Subject: [PATCH 145/210] added photo_url to login output --- src/interfaces/users.interface.ts | 3 ++- src/routes/auth.route.ts | 2 +- src/services/auth.service.ts | 3 ++- src/swagger-output.json | 4 ++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index 3fb0ede..cc98830 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -20,7 +20,7 @@ export interface User { created_at: Date; modified_at: Date; deleted_at?: Date; - + photo_url?: string; patient?: Patient; doctor?: Partial; appointments_as_patient?: Appointment[]; @@ -64,6 +64,7 @@ export interface UserLoginData { role: Role, isVerified: Boolean, hasCompletedProfile: Boolean, + photo_url?: string; doctor?: { specialization: string; account_status: DoctorAccountStatus; diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 0327790..c67618a 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -77,7 +77,7 @@ export class AuthRoute implements Routes { #swagger.responses[200] = { description: 'Login successful', schema: { - data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT' , doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, + data: { id: 1, email: 'user@example.com', name: 'John Doe', role: 'PATIENT', photo_url: 'https://example.com/photo.jpg', doctor: { specialization: 'Cardiology', account_status: 'APPROVED' } }, messageEn: 'Logged In Successfully', messageAr: "تم تسجيل الدخول بنجاح" } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 5f14172..4099a54 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -78,7 +78,7 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - const { name, gender, date_of_birth, email, isVerified, username, phone, role, hasCompletedProfile, doctor } = findUser; + const { name, gender, date_of_birth, email, isVerified, username, phone, role, hasCompletedProfile, doctor, photo_url } = findUser; const patientLoginData: UserLoginData = { name, email, @@ -89,6 +89,7 @@ export class AuthService { role, isVerified, hasCompletedProfile, + photo_url, doctor: doctor ? { specialization: doctor.specialization, account_status: doctor.account_status diff --git a/src/swagger-output.json b/src/swagger-output.json index 6946e2a..6867771 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -214,6 +214,10 @@ "type": "string", "example": "PATIENT" }, + "photo_url": { + "type": "string", + "example": "https://example.com/photo.jpg" + }, "doctor": { "type": "object", "properties": { From db83efaaa720b0ef80ab47de47aa086cb034ed5d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 12 Feb 2026 23:15:38 +0200 Subject: [PATCH 146/210] fix: caching problem in todays appointment route --- src/interfaces/appointments.interface.ts | 2 + src/routes/appointment.route.ts | 107 ++++++++++++----------- src/services/appointment.service.ts | 6 ++ 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 64f80ba..a38e913 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -33,6 +33,7 @@ export interface PatientAppointment { end_time: string; clinic_name: string | null; clinic_address: string | null; + address_maps_link: string | null; } export interface PatientTodayAppointment { @@ -46,6 +47,7 @@ export interface PatientTodayAppointment { end_time: string; clinic_name: string | null; clinic_address: string | null; + address_maps_link: string | null; position: number; estimatedWaitMinutes: number; patientsAhead: number; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 059e0b0..5837552 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -388,7 +388,8 @@ export class AppointmentRoute implements Routes { start_time: '09:00', end_time: '09:30', clinic_name: 'New Cairo Medical Clinic', - clinic_address: '123 Main Street, Medical Park' + clinic_address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.app.goo.gl/iKB7dwMcneuUaULYA', }, { id: 'appointment-uuid', @@ -399,7 +400,8 @@ export class AppointmentRoute implements Routes { start_time: '09:00', end_time: '09:20', clinic_name: 'New Cairo Medical Clinic', - clinic_address: '123 Main Street, Medical Park' + clinic_address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.app.goo.gl/iKB7dwMcneuUaULYA', } ] } @@ -415,55 +417,6 @@ export class AppointmentRoute implements Routes { this.appointmentController.getPatientAppointments ); - this.router.get( - `${this.path}/patient/:appointmentId`, - /* - #swagger.path = '/appointments/patient/{appointmentId}' - #swagger.method = 'get' - #swagger.tags = ['Appointments'] - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication (must be a patient)', - required: true, - type: 'string' - } - #swagger.description = 'Get details of a specific appointment for the patient' - #swagger.parameters['appointmentId'] = { - in: 'path', - description: 'Appointment ID', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Appointment details retrieved successfully', - schema: { - data: { - id: 'appointment-uuid', - status: 'CONFIRMED', - slot_duration: 30, - doctor_name: 'Dr. House', - appointment_date: '2026-02-03', - start_time: '09:00', - end_time: '09:30', - clinic_name: 'New Cairo Medical Clinic', - clinic_address: '123 Main Street, Medical Park' - } - } - } - #swagger.responses[400] = { - description: 'Bad request - patient ID missing' - } - #swagger.responses[401] = { - description: 'Unauthorized - patient not authenticated' - } - #swagger.responses[404] = { - description: 'Appointment not found or does not belong to the patient' - } - */ - AuthMiddleware, - this.appointmentController.getPatientSelectedAppointment - ); - this.router.get( `${this.path}/patient/today-appointment`, /* @@ -494,6 +447,7 @@ export class AppointmentRoute implements Routes { end_time: '09:30', clinic_name: 'New Cairo Medical Clinic', clinic_address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.app.goo.gl/iKB7dwMcneuUaULYA', position: 5, estimatedWaitMinutes: 60, patientsAhead: 3 @@ -509,6 +463,7 @@ export class AppointmentRoute implements Routes { end_time: '14:50', clinic_name: 'Downtown Clinic', clinic_address: '456 Nile Corniche', + address_maps_link: 'https://maps.app.goo.gl/iKB7dwMcneuUaULYA', position: null, estimatedWaitMinutes: null, patientsAhead: null @@ -530,6 +485,56 @@ export class AppointmentRoute implements Routes { this.appointmentController.getTodayAppointment ); + this.router.get( + `${this.path}/patient/:appointmentId`, + /* + #swagger.path = '/appointments/patient/{appointmentId}' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a patient)', + required: true, + type: 'string' + } + #swagger.description = 'Get details of a specific appointment for the patient' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'Appointment ID', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Appointment details retrieved successfully', + schema: { + data: { + id: 'appointment-uuid', + status: 'CONFIRMED', + slot_duration: 30, + doctor_name: 'Dr. House', + appointment_date: '2026-02-03', + start_time: '09:00', + end_time: '09:30', + clinic_name: 'New Cairo Medical Clinic', + clinic_address: '123 Main Street, Medical Park', + address_maps_link: 'https://maps.app.goo.gl/iKB7dwMcneuUaULYA', + } + } + } + #swagger.responses[400] = { + description: 'Bad request - patient ID missing' + } + #swagger.responses[401] = { + description: 'Unauthorized - patient not authenticated' + } + #swagger.responses[404] = { + description: 'Appointment not found or does not belong to the patient' + } + */ + AuthMiddleware, + this.appointmentController.getPatientSelectedAppointment + ); + this.router.patch( `${this.path}/patient/:appointmentId/reschedule`, /* diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index e7d9074..500fe55 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -295,6 +295,7 @@ export class AppointmentService { select: { name: true, address: true, + address_maps_link: true, } } }, @@ -313,6 +314,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, + address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, })); } @@ -338,6 +340,7 @@ export class AppointmentService { select: { name: true, address: true, + address_maps_link: true, } } } @@ -357,6 +360,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, + address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, }; } @@ -401,6 +405,7 @@ export class AppointmentService { select: { name: true, address: true, + address_maps_link: true, } } }, @@ -443,6 +448,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, + address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, position: appointment.position, estimatedWaitMinutes: appointment.estimated_time, patientsAhead: appointment.patients_ahead From 898648bb414f0fcc6392812ce771bc9164396141 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 12 Feb 2026 23:16:30 +0200 Subject: [PATCH 147/210] update swagger --- src/swagger-output.json | 186 +++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 87 deletions(-) diff --git a/src/swagger-output.json b/src/swagger-output.json index 6867771..e2544cc 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4432,6 +4432,10 @@ "clinic_address": { "type": "string", "example": "123 Main Street, Medical Park" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.app.goo.gl/iKB7dwMcneuUaULYA" } } } @@ -4451,93 +4455,6 @@ } } }, - "/appointments/patient/{appointmentId}": { - "get": { - "tags": [ - "Appointments" - ], - "description": "Get details of a specific appointment for the patient", - "parameters": [ - { - "name": "appointmentId", - "in": "path", - "required": true, - "type": "string", - "description": "Appointment ID" - }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication (must be a patient)", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Appointment details retrieved successfully", - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "appointment-uuid" - }, - "status": { - "type": "string", - "example": "CONFIRMED" - }, - "slot_duration": { - "type": "number", - "example": 30 - }, - "doctor_name": { - "type": "string", - "example": "Dr. House" - }, - "appointment_date": { - "type": "string", - "example": "2026-02-03" - }, - "start_time": { - "type": "string", - "example": "09:00" - }, - "end_time": { - "type": "string", - "example": "09:30" - }, - "clinic_name": { - "type": "string", - "example": "New Cairo Medical Clinic" - }, - "clinic_address": { - "type": "string", - "example": "123 Main Street, Medical Park" - } - } - } - }, - "xml": { - "name": "main" - } - } - }, - "400": { - "description": "Bad request - patient ID missing" - }, - "401": { - "description": "Unauthorized - patient not authenticated" - }, - "404": { - "description": "Appointment not found or does not belong to the patient" - } - } - } - }, "/appointments/patient/today-appointment": { "get": { "tags": [ @@ -4609,6 +4526,10 @@ "type": "string", "example": "456 Nile Corniche" }, + "address_maps_link": { + "type": "string", + "example": "https://maps.app.goo.gl/iKB7dwMcneuUaULYA" + }, "position": {}, "estimatedWaitMinutes": {}, "patientsAhead": {} @@ -4633,6 +4554,97 @@ } } }, + "/appointments/patient/{appointmentId}": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get details of a specific appointment for the patient", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "Appointment ID" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a patient)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Appointment details retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "appointment-uuid" + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "slot_duration": { + "type": "number", + "example": 30 + }, + "doctor_name": { + "type": "string", + "example": "Dr. House" + }, + "appointment_date": { + "type": "string", + "example": "2026-02-03" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "09:30" + }, + "clinic_name": { + "type": "string", + "example": "New Cairo Medical Clinic" + }, + "clinic_address": { + "type": "string", + "example": "123 Main Street, Medical Park" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.app.goo.gl/iKB7dwMcneuUaULYA" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - patient ID missing" + }, + "401": { + "description": "Unauthorized - patient not authenticated" + }, + "404": { + "description": "Appointment not found or does not belong to the patient" + } + } + } + }, "/appointments/patient/{appointmentId}/reschedule": { "patch": { "tags": [ From e3ead65cfe10dc1b37777c0884cd96bff57b7b66 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 14 Feb 2026 16:44:40 +0200 Subject: [PATCH 148/210] add doctor's profile pic in patient appointments --- src/interfaces/appointments.interface.ts | 2 ++ src/routes/appointment.route.ts | 5 +++++ src/services/appointment.service.ts | 6 ++++++ src/swagger-output.json | 12 ++++++++++++ 4 files changed, 25 insertions(+) diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index a38e913..85250de 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -28,6 +28,7 @@ export interface PatientAppointment { is_online: boolean; slot_duration: number; doctor_name: string; + doctor_profile_pic: string; appointment_date: string; start_time: string; end_time: string; @@ -42,6 +43,7 @@ export interface PatientTodayAppointment { is_online: boolean; slot_duration: number; doctor_name: string; + doctor_profile_pic: string; appointment_date: string; start_time: string; end_time: string; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 5837552..849614b 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -384,6 +384,7 @@ export class AppointmentRoute implements Routes { status: 'CONFIRMED', slot_duration: 30, doctor_name: 'Dr. House', + doctor_profile_pic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', appointment_date: '2026-02-03', start_time: '09:00', end_time: '09:30', @@ -396,6 +397,7 @@ export class AppointmentRoute implements Routes { status: 'CONFIRMED', slot_duration: 20, doctor_name: 'Dr. House', + doctor_profile_pic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', appointment_date: '2026-03-03', start_time: '09:00', end_time: '09:20', @@ -442,6 +444,7 @@ export class AppointmentRoute implements Routes { is_online: true, slot_duration: 30, doctor_name: 'Dr. House', + doctor_profile_pic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', appointment_date: '2026-02-05', start_time: '09:00', end_time: '09:30', @@ -458,6 +461,7 @@ export class AppointmentRoute implements Routes { is_online: false, slot_duration: 20, doctor_name: 'Dr. Wilson', + doctor_profile_pic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', appointment_date: '2026-02-05', start_time: '14:30', end_time: '14:50', @@ -512,6 +516,7 @@ export class AppointmentRoute implements Routes { status: 'CONFIRMED', slot_duration: 30, doctor_name: 'Dr. House', + doctor_profile_pic: 'https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg', appointment_date: '2026-02-03', start_time: '09:00', end_time: '09:30', diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 500fe55..5b7b190 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -289,6 +289,7 @@ export class AppointmentService { doctor: { select: { name: true, + photo_url: true, } }, clinic: { @@ -309,6 +310,7 @@ export class AppointmentService { is_online: appointment.is_online, slot_duration: appointment.slot_duration, doctor_name: appointment.doctor.name, + doctor_profile_pic: appointment.doctor.photo_url, appointment_date: this.formatDate(appointment.scheduled_time), start_time: this.formatTime(appointment.scheduled_time), end_time: this.formatTime(appointment.end_time), @@ -334,6 +336,7 @@ export class AppointmentService { doctor: { select: { name: true, + photo_url: true, } }, clinic: { @@ -355,6 +358,7 @@ export class AppointmentService { is_online: appointment.is_online, slot_duration: appointment.slot_duration, doctor_name: appointment.doctor.name, + doctor_profile_pic: appointment.doctor.photo_url, appointment_date: this.formatDate(appointment.scheduled_time), start_time: this.formatTime(appointment.scheduled_time), end_time: this.formatTime(appointment.end_time), @@ -399,6 +403,7 @@ export class AppointmentService { doctor: { select: { name: true, + photo_url: true, } }, clinic: { @@ -443,6 +448,7 @@ export class AppointmentService { is_online: appointment.is_online, slot_duration: appointment.slot_duration, doctor_name: appointment.doctor.name, + doctor_profile_pic: appointment.doctor.photo_url, appointment_date: this.formatDate(appointment.scheduled_time), start_time: this.formatTime(appointment.scheduled_time), end_time: this.formatTime(appointment.end_time), diff --git a/src/swagger-output.json b/src/swagger-output.json index e2544cc..4fa59a9 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4413,6 +4413,10 @@ "type": "string", "example": "Dr. House" }, + "doctor_profile_pic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" + }, "appointment_date": { "type": "string", "example": "2026-03-03" @@ -4506,6 +4510,10 @@ "type": "string", "example": "Dr. Wilson" }, + "doctor_profile_pic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" + }, "appointment_date": { "type": "string", "example": "2026-02-05" @@ -4601,6 +4609,10 @@ "type": "string", "example": "Dr. House" }, + "doctor_profile_pic": { + "type": "string", + "example": "https://res.cloudinary.com/deh1n7kqj/image/upload/v1770577124/DOCTORS/profile_pictures/DOCTOR_102ef1ca-3084-41f3-a225-1058e7059ee8_profile_picture_1770577124527.jpg" + }, "appointment_date": { "type": "string", "example": "2026-02-03" From 3436fc49054f01dee743f0f8915a52df9333ea67 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 14 Feb 2026 19:03:09 +0200 Subject: [PATCH 149/210] add doctor/clinic ids in appointments --- src/interfaces/appointments.interface.ts | 4 ++++ src/routes/appointment.route.ts | 10 ++++++++++ src/services/appointment.service.ts | 12 ++++++++++++ src/swagger-output.json | 21 +++++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index 85250de..da60627 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -24,6 +24,8 @@ export interface Appointment { export interface PatientAppointment { id: string; + doctor_id: string; + clinic_id: string | null; status: AppointmentStatus; is_online: boolean; slot_duration: number; @@ -39,6 +41,8 @@ export interface PatientAppointment { export interface PatientTodayAppointment { id: string; + doctor_id: string; + clinic_id: string | null; status: AppointmentStatus; is_online: boolean; slot_duration: number; diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 849614b..e1bda69 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -381,6 +381,8 @@ export class AppointmentRoute implements Routes { data: [ { id: 'appointment-uuid', + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', status: 'CONFIRMED', slot_duration: 30, doctor_name: 'Dr. House', @@ -394,6 +396,8 @@ export class AppointmentRoute implements Routes { }, { id: 'appointment-uuid', + doctorId: 'doctor-uuid', + clinicId: null, status: 'CONFIRMED', slot_duration: 20, doctor_name: 'Dr. House', @@ -440,6 +444,8 @@ export class AppointmentRoute implements Routes { data: [ { id: 'appointment-uuid-1', + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', status: 'CONFIRMED', is_online: true, slot_duration: 30, @@ -457,6 +463,8 @@ export class AppointmentRoute implements Routes { }, { id: 'appointment-uuid-2', + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', status: 'CONFIRMED', is_online: false, slot_duration: 20, @@ -513,6 +521,8 @@ export class AppointmentRoute implements Routes { schema: { data: { id: 'appointment-uuid', + doctorId: 'doctor-uuid', + clinicId: 'clinic-uuid', status: 'CONFIRMED', slot_duration: 30, doctor_name: 'Dr. House', diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 5b7b190..ffadb7d 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -288,12 +288,14 @@ export class AppointmentService { end_time: true, doctor: { select: { + id: true, name: true, photo_url: true, } }, clinic: { select: { + id: true, name: true, address: true, address_maps_link: true, @@ -306,6 +308,8 @@ export class AppointmentService { }); return appointments.map(appointment => ({ id: appointment.id, + doctor_id: appointment.doctor.id, + clinic_id: appointment.clinic ? appointment.clinic.id : null, status: appointment.status, is_online: appointment.is_online, slot_duration: appointment.slot_duration, @@ -335,12 +339,14 @@ export class AppointmentService { end_time: true, doctor: { select: { + id: true, name: true, photo_url: true, } }, clinic: { select: { + id: true, name: true, address: true, address_maps_link: true, @@ -354,6 +360,8 @@ export class AppointmentService { return { id: appointment.id, + doctor_id: appointment.doctor.id, + clinic_id: appointment.clinic ? appointment.clinic.id : null, status: appointment.status, is_online: appointment.is_online, slot_duration: appointment.slot_duration, @@ -402,12 +410,14 @@ export class AppointmentService { patients_ahead: true, doctor: { select: { + id: true, name: true, photo_url: true, } }, clinic: { select: { + id: true, name: true, address: true, address_maps_link: true, @@ -444,6 +454,8 @@ export class AppointmentService { result.push({ id: appointment.id, + doctor_id: appointment.doctor.id, + clinic_id: appointment.clinic ? appointment.clinic.id : null, status: appointment.status, is_online: appointment.is_online, slot_duration: appointment.slot_duration, diff --git a/src/swagger-output.json b/src/swagger-output.json index 4fa59a9..54fdd7e 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4401,6 +4401,11 @@ "type": "string", "example": "appointment-uuid" }, + "doctorId": { + "type": "string", + "example": "doctor-uuid" + }, + "clinicId": {}, "status": { "type": "string", "example": "CONFIRMED" @@ -4494,6 +4499,14 @@ "type": "string", "example": "appointment-uuid-2" }, + "doctorId": { + "type": "string", + "example": "doctor-uuid" + }, + "clinicId": { + "type": "string", + "example": "clinic-uuid" + }, "status": { "type": "string", "example": "CONFIRMED" @@ -4597,6 +4610,14 @@ "type": "string", "example": "appointment-uuid" }, + "doctorId": { + "type": "string", + "example": "doctor-uuid" + }, + "clinicId": { + "type": "string", + "example": "clinic-uuid" + }, "status": { "type": "string", "example": "CONFIRMED" From 2f16d35d8ef367b9c2ac08af62d3804ff5f0bdcc Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 17 Feb 2026 12:51:57 +0200 Subject: [PATCH 150/210] return todays appointments without filteration --- src/services/appointment.service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index ffadb7d..a84aae4 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -392,11 +392,6 @@ export class AppointmentService { gte: today, lte: endOfToday, }, - deleted_at: null, - status: { - in: ['CONFIRMED', 'COMPLETED'] - }, - }, select: { id: true, @@ -1014,8 +1009,6 @@ export class AppointmentService { gte: startOfDay, lte: endOfDay }, - status: 'CONFIRMED', - deleted_at: null, }, orderBy: { scheduled_time: 'asc', From 30e974494c681299dd6d795b138c1f01a35ef139 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 17 Feb 2026 15:23:12 +0200 Subject: [PATCH 151/210] update schema: add nurse, announcements, scheduling Added Nurse table with profile details announcements table created announcements_nurse table nurseSchedule table for shift management --- .../migration.sql | 160 +++++++++++++ src/prisma/schema.prisma | 221 ++++++++++++++---- 2 files changed, 336 insertions(+), 45 deletions(-) create mode 100644 src/prisma/migrations/20260217131027_add_nurse_announcements_tables/migration.sql diff --git a/src/prisma/migrations/20260217131027_add_nurse_announcements_tables/migration.sql b/src/prisma/migrations/20260217131027_add_nurse_announcements_tables/migration.sql new file mode 100644 index 0000000..62e861e --- /dev/null +++ b/src/prisma/migrations/20260217131027_add_nurse_announcements_tables/migration.sql @@ -0,0 +1,160 @@ +-- CreateEnum +CREATE TYPE "NurseAccountStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); + +-- CreateEnum +CREATE TYPE "AnnouncementStatus" AS ENUM ('POSTED', 'PENDING', 'EXPIRED'); + +-- CreateEnum +CREATE TYPE "AnnouncementNurseStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); + +-- CreateTable +CREATE TABLE "Nurse" ( + "id" TEXT NOT NULL, + "account_status" "NurseAccountStatus" NOT NULL DEFAULT 'PENDING', + "years_of_experience" INTEGER NOT NULL, + "national_id_url" VARCHAR(500), + "national_id_public_id" VARCHAR(500), + "bonus_file_url" VARCHAR(500), + "bonus_file_public_id" VARCHAR(500), + "brief" TEXT, + + CONSTRAINT "Nurse_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "NurseSchedules" ( + "id" TEXT NOT NULL, + "nurse_id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "clinic_id" TEXT, + "day_of_week" "DayOfWeek" NOT NULL, + "start_time" VARCHAR(12) NOT NULL, + "end_time" VARCHAR(12) NOT NULL, + "is_online" BOOLEAN NOT NULL DEFAULT false, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + "userId" TEXT, + + CONSTRAINT "NurseSchedules_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Announcements" ( + "id" TEXT NOT NULL, + "doctor_id" TEXT NOT NULL, + "clinic_id" TEXT NOT NULL, + "status" "AnnouncementStatus" NOT NULL DEFAULT 'PENDING', + "gender" "Gender", + "max_age" INTEGER, + "years_of_experience" INTEGER, + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + "userId" TEXT, + + CONSTRAINT "Announcements_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AnnouncementDays" ( + "id" TEXT NOT NULL, + "announcement_id" TEXT NOT NULL, + "day_of_week" "DayOfWeek" NOT NULL, + "start_time" TEXT NOT NULL, + "end_time" TEXT NOT NULL, + + CONSTRAINT "AnnouncementDays_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AnnouncementNurses" ( + "id" TEXT NOT NULL, + "announcement_id" TEXT NOT NULL, + "nurse_id" TEXT NOT NULL, + "status" "AnnouncementNurseStatus" NOT NULL DEFAULT 'PENDING', + "doctor_id" TEXT, + "clinic_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "userId" TEXT, + + CONSTRAINT "AnnouncementNurses_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "NurseSchedules_nurse_id_idx" ON "NurseSchedules"("nurse_id"); + +-- CreateIndex +CREATE INDEX "NurseSchedules_doctor_id_idx" ON "NurseSchedules"("doctor_id"); + +-- CreateIndex +CREATE INDEX "NurseSchedules_clinic_id_idx" ON "NurseSchedules"("clinic_id"); + +-- CreateIndex +CREATE INDEX "Announcements_doctor_id_idx" ON "Announcements"("doctor_id"); + +-- CreateIndex +CREATE INDEX "Announcements_clinic_id_idx" ON "Announcements"("clinic_id"); + +-- CreateIndex +CREATE INDEX "Announcements_status_idx" ON "Announcements"("status"); + +-- CreateIndex +CREATE INDEX "AnnouncementDays_announcement_id_idx" ON "AnnouncementDays"("announcement_id"); + +-- CreateIndex +CREATE INDEX "AnnouncementNurses_announcement_id_idx" ON "AnnouncementNurses"("announcement_id"); + +-- CreateIndex +CREATE INDEX "AnnouncementNurses_nurse_id_idx" ON "AnnouncementNurses"("nurse_id"); + +-- CreateIndex +CREATE INDEX "AnnouncementNurses_doctor_id_idx" ON "AnnouncementNurses"("doctor_id"); + +-- CreateIndex +CREATE INDEX "AnnouncementNurses_clinic_id_idx" ON "AnnouncementNurses"("clinic_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "AnnouncementNurses_announcement_id_nurse_id_key" ON "AnnouncementNurses"("announcement_id", "nurse_id"); + +-- AddForeignKey +ALTER TABLE "Nurse" ADD CONSTRAINT "Nurse_id_fkey" FOREIGN KEY ("id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NurseSchedules" ADD CONSTRAINT "NurseSchedules_nurse_id_fkey" FOREIGN KEY ("nurse_id") REFERENCES "Nurse"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NurseSchedules" ADD CONSTRAINT "NurseSchedules_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NurseSchedules" ADD CONSTRAINT "NurseSchedules_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Announcements" ADD CONSTRAINT "Announcements_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Announcements" ADD CONSTRAINT "Announcements_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Announcements" ADD CONSTRAINT "Announcements_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementDays" ADD CONSTRAINT "AnnouncementDays_announcement_id_fkey" FOREIGN KEY ("announcement_id") REFERENCES "Announcements"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementNurses" ADD CONSTRAINT "AnnouncementNurses_announcement_id_fkey" FOREIGN KEY ("announcement_id") REFERENCES "Announcements"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementNurses" ADD CONSTRAINT "AnnouncementNurses_nurse_id_fkey" FOREIGN KEY ("nurse_id") REFERENCES "Nurse"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementNurses" ADD CONSTRAINT "AnnouncementNurses_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementNurses" ADD CONSTRAINT "AnnouncementNurses_clinic_id_fkey" FOREIGN KEY ("clinic_id") REFERENCES "Clinic"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AnnouncementNurses" ADD CONSTRAINT "AnnouncementNurses_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 567ffb4..09f8348 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -8,39 +8,43 @@ datasource db { } model User { - id String @id @default(uuid()) - name String @db.VarChar(255) - email String @unique @db.VarChar(255) - username String @unique @db.VarChar(255) - phone String @db.VarChar(20) - password_hash String @db.VarChar(255) + id String @id @default(uuid()) + name String @db.VarChar(255) + email String @unique @db.VarChar(255) + username String @unique @db.VarChar(255) + phone String @db.VarChar(20) + password_hash String @db.VarChar(255) gender Gender date_of_birth DateTime - created_at DateTime @default(now()) - modified_at DateTime @updatedAt + created_at DateTime @default(now()) + modified_at DateTime @updatedAt deleted_at DateTime? - email_OTP String? @db.VarChar(6) + email_OTP String? @db.VarChar(6) email_OTP_expires_at DateTime? - isVerified Boolean @default(false) - password_reset_token String? @db.VarChar(255) + isVerified Boolean @default(false) + password_reset_token String? @db.VarChar(255) password_reset_token_expires_at DateTime? - role Role @default(PATIENT) - hasCompletedProfile Boolean @default(false) - photo_public_id String? @db.VarChar(500) - photo_url String? @db.VarChar(500) - appointments_as_doctor Appointment[] @relation("DoctorAppointments") - appointments_as_patient Appointment[] @relation("PatientAppointments") - audit_logs AuditLog[] @relation("UserAuditLogs") - clinics_as_nurse ClinicNurse[] @relation("NurseClinics") - doctor Doctor? @relation("UserAsDoctor") - medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") - medications_as_doctor Medication[] @relation("DoctorMedications") - medications_as_patient Medication[] @relation("PatientMedications") - controlled_patients Patient[] @relation("ControllingNurse") - patient Patient? @relation("UserAsPatient") - refresh_tokens RefreshToken[] @relation("UserRefreshTokens") - scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") - scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + role Role @default(PATIENT) + hasCompletedProfile Boolean @default(false) + photo_public_id String? @db.VarChar(500) + photo_url String? @db.VarChar(500) + appointments_as_doctor Appointment[] @relation("DoctorAppointments") + appointments_as_patient Appointment[] @relation("PatientAppointments") + audit_logs AuditLog[] @relation("UserAuditLogs") + clinics_as_nurse ClinicNurse[] @relation("NurseClinics") + doctor Doctor? @relation("UserAsDoctor") + medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") + medications_as_doctor Medication[] @relation("DoctorMedications") + medications_as_patient Medication[] @relation("PatientMedications") + controlled_patients Patient[] @relation("ControllingNurse") + patient Patient? @relation("UserAsPatient") + refresh_tokens RefreshToken[] @relation("UserRefreshTokens") + scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") + scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + nurse Nurse? @relation("UserAsNurse") + nurseSchedules NurseSchedule[] + announcements Announcement[] + announcementNurses AnnouncementNurse[] @@map("Users") } @@ -69,6 +73,8 @@ model Doctor { user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) doctorSchedules DoctorSchedule[] vacations Vacation[] + announcements Announcement[] @relation("AnnouncementDoctor") + announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseDoctor") @@map("Doctor") } @@ -84,6 +90,22 @@ model Patient { @@map("Patient") } +model Nurse { + id String @id @default(uuid()) + account_status NurseAccountStatus @default(PENDING) + years_of_experience Int + national_id_url String? @db.VarChar(500) + national_id_public_id String? @db.VarChar(500) + bonus_file_url String? @db.VarChar(500) + bonus_file_public_id String? @db.VarChar(500) + brief String? + user User @relation("UserAsNurse", fields: [id], references: [id], onDelete: Cascade) + nurse_schedules NurseSchedule[] @relation("NurseScheduleNurse") + announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurseNurse") + + @@map("Nurse") +} + model Appointment { id String @id @default(uuid()) patient_id String? @@ -159,23 +181,26 @@ model ScanLab { } model Clinic { - id String @id @default(uuid()) - is_active Boolean @default(true) - address String @db.VarChar(300) - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? - address_maps_link String? @db.VarChar(500) - canPayOnline Boolean @default(false) - phone String @db.VarChar(20) - created_by String @db.VarChar(255) - name String @db.VarChar(255) - opening_at String @db.VarChar(12) - closing_at String @db.VarChar(12) - appointments Appointment[] - clinic_doctors ClinicDoctor[] - clinic_nurses ClinicNurse[] - doctorSchedules DoctorSchedule[] + id String @id @default(uuid()) + is_active Boolean @default(true) + address String @db.VarChar(300) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + address_maps_link String? @db.VarChar(500) + canPayOnline Boolean @default(false) + phone String @db.VarChar(20) + created_by String @db.VarChar(255) + name String @db.VarChar(255) + opening_at String @db.VarChar(12) + closing_at String @db.VarChar(12) + appointments Appointment[] + clinic_doctors ClinicDoctor[] + clinic_nurses ClinicNurse[] + doctorSchedules DoctorSchedule[] + nurseSchedules NurseSchedule[] + announcements Announcement[] @relation("AnnouncementClinic") + announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseClinic") @@map("Clinic") } @@ -278,6 +303,30 @@ model DoctorSchedule { @@map("DoctorSchedules") } +model NurseSchedule { + id String @id @default(uuid()) + nurse_id String + doctor_id String + clinic_id String? + day_of_week DayOfWeek + start_time String @db.VarChar(12) + end_time String @db.VarChar(12) + is_online Boolean @default(false) + is_active Boolean @default(true) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + nurse Nurse @relation("NurseScheduleNurse", fields: [nurse_id], references: [id], onDelete: Cascade) + clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id]) + userId String? + + @@index([nurse_id]) + @@index([doctor_id]) + @@index([clinic_id]) + @@map("NurseSchedules") +} + model Vacation { id String @id @default(uuid()) doctor_id String @@ -299,6 +348,70 @@ model Vacation { @@map("Vacations") } +model Announcement { + id String @id @default(uuid()) + doctor_id String + clinic_id String + status AnnouncementStatus @default(PENDING) + gender Gender? + max_age Int? + years_of_experience Int? + notes String? + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + doctor Doctor @relation("AnnouncementDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) + clinic Clinic @relation("AnnouncementClinic", fields: [clinic_id], references: [id], onDelete: Cascade) + working_days AnnouncementDay[] + announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurses") + user User? @relation(fields: [userId], references: [id]) + userId String? + + @@index([doctor_id]) + @@index([clinic_id]) + @@index([status]) + @@map("Announcements") +} + +model AnnouncementDay { + id String @id @default(uuid()) + announcement_id String + day_of_week DayOfWeek + start_time String + end_time String + announcement Announcement @relation(fields: [announcement_id], references: [id], onDelete: Cascade) + + @@index([announcement_id]) + @@map("AnnouncementDays") +} + +model AnnouncementNurse { + id String @id @default(uuid()) + announcement_id String + nurse_id String + status AnnouncementNurseStatus @default(PENDING) + doctor_id String? + clinic_id String? + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + + announcement Announcement @relation("AnnouncementNurses", fields: [announcement_id], references: [id], onDelete: Cascade) + + nurse Nurse @relation("AnnouncementNurseNurse", fields: [nurse_id], references: [id], onDelete: Cascade) + doctor Doctor? @relation("AnnouncementNurseDoctor", fields: [doctor_id], references: [id]) + clinic Clinic? @relation("AnnouncementNurseClinic", fields: [clinic_id], references: [id]) + user User? @relation(fields: [userId], references: [id]) + userId String? + + @@unique([announcement_id, nurse_id]) + @@index([announcement_id]) + @@index([nurse_id]) + @@index([doctor_id]) + @@index([clinic_id]) + @@map("AnnouncementNurses") +} + enum VacationStatus { UPCOMING CURRENT @@ -375,3 +488,21 @@ enum AppointmentStatus { CANCELLED NO_SHOW } + +enum NurseAccountStatus { + PENDING + APPROVED + REJECTED +} + +enum AnnouncementStatus { + POSTED + PENDING + EXPIRED +} + +enum AnnouncementNurseStatus { + PENDING + APPROVED + REJECTED +} From 095863c73aa530b0e56e85ddaab7ae909eed7dc4 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 17 Feb 2026 19:20:08 +0200 Subject: [PATCH 152/210] nurse login/signup/set password --- src/controllers/nurse.controller.ts | 48 ++++ src/dtos/nurses.dto.ts | 59 +++++ src/interfaces/enums.interface.ts | 6 + src/interfaces/nurse.interface.ts | 14 ++ .../migration.sql | 18 ++ src/prisma/schema.prisma | 22 +- src/routes/nurse.route.ts | 29 +++ src/services/nurse.service.ts | 227 ++++++++++++++++++ src/utils/errorMessages.ts | 10 + src/utils/responseMessages.ts | 14 ++ 10 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 src/controllers/nurse.controller.ts create mode 100644 src/dtos/nurses.dto.ts create mode 100644 src/interfaces/nurse.interface.ts create mode 100644 src/prisma/migrations/20260217152715_modify_file_fields_for_nurse/migration.sql create mode 100644 src/routes/nurse.route.ts create mode 100644 src/services/nurse.service.ts diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts new file mode 100644 index 0000000..e7db182 --- /dev/null +++ b/src/controllers/nurse.controller.ts @@ -0,0 +1,48 @@ +import { Request, Response, NextFunction } from "express"; +import { RequestWithUser } from "@/interfaces"; +import { HttpException } from "@/exceptions/HttpException"; +import { catchAsync } from '@/utils/catchAsync'; +import { NurseService } from "@/services/nurse.service"; +import Container from "typedi"; +import { NurseSignupRequestDto, NurseLoginRequestDto, NurseSetPasswordRequestDto} from "@/dtos/nurses.dto"; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; + +export class NurseController { + private nurseService = Container.get(NurseService); + + public nurseSignup = catchAsync(async (req: Request, res: Response, next: NextFunction) => { + const nurseData: NurseSignupRequestDto = req.body; + const nurseFiles = req.files as Express.Multer.File[]; + await this.nurseService.signup(nurseData, nurseFiles); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_CREATED_WAITING_VERIFICATION); + res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); + + public nurseLogin = async (req: Request, res: Response, next: NextFunction) => { + const nurseLoginData: NurseLoginRequestDto = req.body; + const loginResult = await this.nurseService.login(nurseLoginData); + + if (loginResult === false) { + res.redirect('/test') + } else if (typeof loginResult === 'object') { + const { cookies, NurseAccountData } = loginResult; + res.setHeader('Set-Cookie', cookies); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_RETRIEVED); + res.status(200).json({ + data: NurseAccountData, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr + }); + } + } + + public nurseSetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { + const nurseId = req.user?.id; + const { password }: NurseSetPasswordRequestDto = req.body; + await this.nurseService.setPassword(nurseId, password); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_SET_SUCCESSFULLY_BY_NURSE); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); + +} \ No newline at end of file diff --git a/src/dtos/nurses.dto.ts b/src/dtos/nurses.dto.ts new file mode 100644 index 0000000..3682779 --- /dev/null +++ b/src/dtos/nurses.dto.ts @@ -0,0 +1,59 @@ +import { Gender } from "@prisma/client"; +import { IsString, IsNotEmpty, IsEmail, IsInt } from "class-validator"; + +export class NurseSignupRequestDto { + @IsString() + @IsNotEmpty() + public name: string; + + @IsEmail() + @IsNotEmpty() + public email: string; + + @IsString() + @IsNotEmpty() + public phone: string; + + @IsString() + @IsNotEmpty() + public password: string; + + @IsInt() + @IsNotEmpty() + public years_of_experience: number; + + @IsString() + public brief?: string; + + @IsString() + @IsNotEmpty() + public gender: Gender; + + @IsString() + public date_of_birth?: Date; + + nationalCard: Express.Multer.File; + bonusFile: Express.Multer.File; +} + +export class NurseLoginRequestDto { + @IsNotEmpty() + public emailOrUsername: string; + + @IsString() + @IsNotEmpty() + public password: string; + + @IsString() + public rememberMe?: boolean; +} + +export class NurseSetPasswordRequestDto { + @IsString() + @IsNotEmpty() + public password: string; +} + +export class NurseProfilePictureRequestDto { + profilePicture: Express.Multer.File; +} \ No newline at end of file diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index c8815e5..866427b 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -33,4 +33,10 @@ export enum DOCTOR_FILES { MASTERS_CERTIFICATE = 'mastersCertificate', FELLOWSHIP_CERTIFICATE = 'fellowshipCertificate', UNION_SPECIALIZATION_CERTIFICATE = 'unionSpecializationCertificate', +} + +export enum NURSE_FILES { + NATIONAL_CARD = 'nationalCard', + BONUS_FILE = 'bonusFile', + } \ No newline at end of file diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts new file mode 100644 index 0000000..6ad303d --- /dev/null +++ b/src/interfaces/nurse.interface.ts @@ -0,0 +1,14 @@ +import { NurseAccountStatus} from "@prisma/client"; + +export interface NurseLoginData { + id: string, + name: string, + email: string, + username: string, + phone: string, + gender: string, + nurse: { + account_status: NurseAccountStatus, + } +} + diff --git a/src/prisma/migrations/20260217152715_modify_file_fields_for_nurse/migration.sql b/src/prisma/migrations/20260217152715_modify_file_fields_for_nurse/migration.sql new file mode 100644 index 0000000..c8256a2 --- /dev/null +++ b/src/prisma/migrations/20260217152715_modify_file_fields_for_nurse/migration.sql @@ -0,0 +1,18 @@ +/* + Warnings: + + - You are about to drop the column `bonus_file_public_id` on the `Nurse` table. All the data in the column will be lost. + - You are about to drop the column `bonus_file_url` on the `Nurse` table. All the data in the column will be lost. + - You are about to drop the column `national_id_public_id` on the `Nurse` table. All the data in the column will be lost. + - You are about to drop the column `national_id_url` on the `Nurse` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Nurse" DROP COLUMN "bonus_file_public_id", +DROP COLUMN "bonus_file_url", +DROP COLUMN "national_id_public_id", +DROP COLUMN "national_id_url", +ADD COLUMN "bonusFilePublicId" VARCHAR(500), +ADD COLUMN "bonusFileUrl" VARCHAR(500), +ADD COLUMN "nationalCardPublicId" VARCHAR(500), +ADD COLUMN "nationalCardUrl" VARCHAR(500); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 09f8348..885167b 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -91,17 +91,17 @@ model Patient { } model Nurse { - id String @id @default(uuid()) - account_status NurseAccountStatus @default(PENDING) - years_of_experience Int - national_id_url String? @db.VarChar(500) - national_id_public_id String? @db.VarChar(500) - bonus_file_url String? @db.VarChar(500) - bonus_file_public_id String? @db.VarChar(500) - brief String? - user User @relation("UserAsNurse", fields: [id], references: [id], onDelete: Cascade) - nurse_schedules NurseSchedule[] @relation("NurseScheduleNurse") - announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurseNurse") + id String @id @default(uuid()) + account_status NurseAccountStatus @default(PENDING) + years_of_experience Int + nationalCardUrl String? @db.VarChar(500) + nationalCardPublicId String? @db.VarChar(500) + bonusFileUrl String? @db.VarChar(500) + bonusFilePublicId String? @db.VarChar(500) + brief String? + user User @relation("UserAsNurse", fields: [id], references: [id], onDelete: Cascade) + nurse_schedules NurseSchedule[] @relation("NurseScheduleNurse") + announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurseNurse") @@map("Nurse") } diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts new file mode 100644 index 0000000..a467ae9 --- /dev/null +++ b/src/routes/nurse.route.ts @@ -0,0 +1,29 @@ +import { Routes } from "@/interfaces"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Router } from "express"; +import { errorWrapper } from "@/utils/errorWrapper"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; +import { Role } from "@prisma/client"; +import { uploadPdf } from "@/middlewares/multer.middleware"; + +export class NurseRoute implements Routes { + public path = '/nurses' + public router = Router(); + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.post( + `${this.path}/signup`, + ) + + this.router.post( + `${this.path}/login`, + ); + + this.router.patch( + `${this.path}/set-password`, + ); + } +} \ No newline at end of file diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts new file mode 100644 index 0000000..41acab4 --- /dev/null +++ b/src/services/nurse.service.ts @@ -0,0 +1,227 @@ +import { Service } from "typedi"; +import { HttpException } from "@/exceptions/HttpException"; +import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; +import { hash, compare } from "bcrypt"; +import { AuthService } from "./auth.service"; +import { NurseSignupRequestDto, NurseLoginRequestDto } from "@/dtos/nurses.dto"; +import { NurseLoginData } from "@/interfaces/nurse.interface"; +import { NURSE_FILES } from "@/interfaces"; +import prisma from '@/config/prisma'; +import { Role, NurseAccountStatus } from "@prisma/client"; +import cloudinary from "@/utils/cloudinary"; +import fs from "fs"; + +@Service() +export class NurseService { + + private authService = new AuthService(); + + public async signup(nurseData: NurseSignupRequestDto, nurseFiles: {}) { + const existingUser = await prisma.user.findUnique({ + where: { email: nurseData.email } + }); + + if (existingUser) { + const error = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const username = nurseData.email.split('@')[0]; + + const existingUsername = await prisma.user.findUnique({ + where: { username } + }); + + if (existingUsername) { + const error = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const hashedPassword = await hash(nurseData.password, 10); + + const createdUserId = await prisma.$transaction(async (tx) => { + const createdUser = await tx.user.create({ + data: { + email: nurseData.email, + name: nurseData.name, + username, + phone: nurseData.phone, + gender: nurseData.gender, + date_of_birth: new Date(nurseData.date_of_birth), + password_hash: hashedPassword, + role: Role.NURSE, + isVerified: true, + hasCompletedProfile: true, + }, + }); + + await tx.nurse.create({ + data: { + id: createdUser.id, + account_status: NurseAccountStatus.PENDING, + years_of_experience: nurseData.years_of_experience, + brief: nurseData.brief, + }, + }); + return createdUser.id; + }); + + if (nurseFiles && Object.keys(nurseFiles).length > 0) { + const nurseFilesArray = Object.values(nurseFiles).flat() as Express.Multer.File[]; + + await this._uploadFiles(nurseFilesArray, createdUserId); + } + } + + public async login(nurseLoginData: NurseLoginRequestDto): Promise<{ cookies: string[]; NurseAccountData: NurseLoginData } | boolean> { + + const nurseUserData = await prisma.user.findFirst({ + where: { + OR: [ + { email: nurseLoginData.emailOrUsername }, + { username: nurseLoginData.emailOrUsername } + ] + }, + select: { + id: true, + email: true, + username: true, + name: true, + phone: true, + gender: true, + hasCompletedProfile: true, + password_hash: true, + nurse: { + select: { + account_status: true + } + } + } + }); + + if (!nurseUserData) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const isPasswordMatching = await compare(nurseLoginData.password, nurseUserData.password_hash); + + if (!isPasswordMatching) { + const error = createBilingualError(401, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (nurseUserData.nurse?.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!nurseUserData.hasCompletedProfile) { + return false; + } + + const NurseAccountData: NurseLoginData = + { + id: nurseUserData.id, + name: nurseUserData.name, + email: nurseUserData.email, + username: nurseUserData.username, + phone: nurseUserData.phone, + gender: nurseUserData.gender, + nurse: { + account_status: nurseUserData.nurse?.account_status + } + } + + const token = await this.authService.createTokens(nurseUserData, nurseLoginData.rememberMe); + const cookies = this.authService.createCookies(token); + + return { cookies, NurseAccountData }; + } + + public async setPassword(nurseId: string, password: string): Promise { + const hashedPassword = await hash(password, 10); + const nurseUserData = await prisma.user.findUnique({ + where: { id: nurseId }, + select: { hasCompletedProfile: true } + }); + if (!nurseUserData) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + if (nurseUserData.hasCompletedProfile) { + const error = createBilingualError(400, ErrorMessages.NURSE_PASSWORD_ALREADY_SET); + throw new HttpException(error.status, error.message, error.messageAr); + } + await prisma.user.update({ + where: { id: nurseId }, + data: { + password_hash: hashedPassword, + hasCompletedProfile: true + } + }); + } + + + private async _uploadFiles(files: Express.Multer.File[], nurseId: string): Promise { + const uploadedFiles: { public_id: string }[] = []; + + try { + for (const file of files) { + if (!Object.values(NURSE_FILES).includes(file.fieldname as any)) { + const error = createBilingualError(400, ErrorMessages.UNKNOWN_FILE_FIELDNAME); + throw new HttpException(error.status, error.message, error.messageAr); + } + } + + const uploadResults = await Promise.all( + files.map(file => + cloudinary.uploader.upload(file.path, { + folder: `NURSES/documents/${nurseId}`, + overwrite: false, + public_id: `NURSE_${nurseId}_${file.fieldname}_${Date.now()}` + }) + ) + ); + + uploadedFiles.push(...uploadResults.map(r => ({ public_id: r.public_id }))); + + const updateData: any = {}; + files.forEach((file, index) => { + const uploadResult = uploadResults[index]; + + switch (file.fieldname) { + case NURSE_FILES.NATIONAL_CARD: + updateData.nationalCardUrl = uploadResult.secure_url; + updateData.nationalCardPublicId = uploadResult.public_id; + break; + case NURSE_FILES.BONUS_FILE: + updateData.bonusFileUrl = uploadResult.secure_url; + updateData.bonusFilePublicId = uploadResult.public_id; + break; + } + fs.unlinkSync(file.path); + }); + + await prisma.nurse.update({ + where: { id: nurseId }, + data: updateData + }); + + } catch (error) { + if (uploadedFiles.length > 0) { + await Promise.all( + uploadedFiles.map(f => cloudinary.uploader.destroy(f.public_id).catch(() => { })) + ); + + } + + files.forEach(file => { + if (fs.existsSync(file.path)) { + fs.unlinkSync(file.path); + } + }); + throw error; + } + } +} diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index e35a755..2bd0f08 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -106,6 +106,16 @@ export const ErrorMessages = { en: 'Maximum number of created clinics reached', ar: 'تم الوصول إلى الحد الأقصى لعدد العيادات', }, + + // nurse + NURSE_PASSWORD_ALREADY_SET: { + en: 'Password has already been set', + ar: 'تم تعيين كلمة المرور بالفعل', + }, + NURSE_ACCOUNT_NOT_APPROVED: { + en: 'Nurse account is not approved yet', + ar: 'حساب الممرضة غير مفعل بعد', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 89d70cb..eecf33f 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -111,6 +111,20 @@ export const SuccessResponseMessages = { message_ar: "تم تعيين كلمة المرور بنجاح.", }, + // Success messages for nurses + NURSE_CREATED_WAITING_VERIFICATION: { + message_en: "Nurse account created successfully. Please wait for verification.", + message_ar: "تم إنشاء حساب الممرضة بنجاح. يرجى الانتظار للموافقة عليه.", + }, + NURSE_RETRIEVED: { + message_en: "Nurse retrieved successfully.", + message_ar: "تم استرجاع بيانات الممرضة بنجاح.", + }, + PASSWORD_SET_SUCCESSFULLY_BY_NURSE: { + message_en: 'Password set successfully by nurse', + message_ar: 'تم تعيين كلمة المرور بنجاح من قبل الممرضة', + }, + // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { message_en: "Phone number updated successfully.", From 3cc781421189506af1665143a0d47b05c1d6681f Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 17 Feb 2026 21:10:39 +0200 Subject: [PATCH 153/210] update nurse routes: swagger --- src/controllers/admin.controller.ts | 13 + src/controllers/nurse.controller.ts | 6 +- src/dtos/nurses.dto.ts | 5 +- src/middlewares/multer.middleware.ts | 8 + src/routes/admin.route.ts | 37 +++ src/routes/nurse.route.ts | 177 +++++++++++++- src/server.ts | 4 +- src/services/admin.service.ts | 47 +++- src/services/nurse.service.ts | 13 +- src/swagger-output.json | 348 +++++++++++++++++++++++++++ src/swagger.mjs | 5 +- src/utils/errorMessages.ts | 4 + src/utils/responseMessages.ts | 4 + 13 files changed, 650 insertions(+), 21 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 6a14916..3225e77 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -127,6 +127,19 @@ export class AdminController { }); } + public updateNurseVerificationStatus = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const nurseId = req.params.id; + const { isVerified } = req.body; + await this.adminService.updateNurseVerificationStatus(nurseId, isVerified); + await this.adminService.sendVerificationStatusEmail(nurseId, isVerified); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_VERIFICATION_STATUS_UPDATED); + res.status(200).json({ + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } + // Clinic Routes public getAllClinics = async (req: Request, res: Response, next: NextFunction): Promise => { diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts index e7db182..0f36541 100644 --- a/src/controllers/nurse.controller.ts +++ b/src/controllers/nurse.controller.ts @@ -14,14 +14,14 @@ export class NurseController { public nurseSignup = catchAsync(async (req: Request, res: Response, next: NextFunction) => { const nurseData: NurseSignupRequestDto = req.body; const nurseFiles = req.files as Express.Multer.File[]; - await this.nurseService.signup(nurseData, nurseFiles); + await this.nurseService.nurseSignup(nurseData, nurseFiles); const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_CREATED_WAITING_VERIFICATION); res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); public nurseLogin = async (req: Request, res: Response, next: NextFunction) => { const nurseLoginData: NurseLoginRequestDto = req.body; - const loginResult = await this.nurseService.login(nurseLoginData); + const loginResult = await this.nurseService.nurseLogin(nurseLoginData); if (loginResult === false) { res.redirect('/test') @@ -40,7 +40,7 @@ export class NurseController { public nurseSetPassword = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { const nurseId = req.user?.id; const { password }: NurseSetPasswordRequestDto = req.body; - await this.nurseService.setPassword(nurseId, password); + await this.nurseService.nurseSetPassword(nurseId, password); const responseMessage = createMultiLangMessage(SuccessResponseMessages.PASSWORD_SET_SUCCESSFULLY_BY_NURSE); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); diff --git a/src/dtos/nurses.dto.ts b/src/dtos/nurses.dto.ts index 3682779..f684fb2 100644 --- a/src/dtos/nurses.dto.ts +++ b/src/dtos/nurses.dto.ts @@ -1,5 +1,6 @@ import { Gender } from "@prisma/client"; -import { IsString, IsNotEmpty, IsEmail, IsInt } from "class-validator"; +import { IsString, IsNotEmpty, IsEmail, IsInt, IsOptional } from "class-validator"; +import { Type } from "class-transformer"; export class NurseSignupRequestDto { @IsString() @@ -19,10 +20,12 @@ export class NurseSignupRequestDto { public password: string; @IsInt() + @Type(() => Number) @IsNotEmpty() public years_of_experience: number; @IsString() + @IsOptional() public brief?: string; @IsString() diff --git a/src/middlewares/multer.middleware.ts b/src/middlewares/multer.middleware.ts index 120c224..9dce826 100644 --- a/src/middlewares/multer.middleware.ts +++ b/src/middlewares/multer.middleware.ts @@ -1,9 +1,17 @@ import multer from 'multer'; import path from 'path'; +import fs from 'fs'; import { Request } from 'express'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { HttpException } from '@/exceptions/HttpException'; +const uploadDir = path.join(process.cwd(), 'uploads'); + +if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); + console.log(`Created uploads directory: ${uploadDir}`); +} + // We use diskStorage so the file is saved to a 'temp' folder first. const storage = multer.diskStorage({ destination: (req: Request, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => { diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 69ae409..be21622 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -175,6 +175,43 @@ export class AdminRoute implements Routes { this.adminController.updateDoctorVerificationStatus, ); + this.router.patch( + '/admin/nurses/verify/:id', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Nurse ID', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Verification status', + required: true, + schema: { + $isApproved: true + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie or Authorization header)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurse verification status updated successfully', + schema: { + messageEn: 'Nurse verification status updated successfully', + messageAr: "تم تحديث حالة اعتماد الممرض بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.updateNurseVerificationStatus, + ); + this.router.get( '/admin/doctors/:id', /* diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index a467ae9..b6f2c09 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -1,7 +1,8 @@ import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; -import { errorWrapper } from "@/utils/errorWrapper"; +import { NurseController } from "@/controllers/nurse.controller"; +import { NurseLoginRequestDto, NurseSetPasswordRequestDto, NurseSignupRequestDto } from "@/dtos/nurses.dto"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { Role } from "@prisma/client"; import { uploadPdf } from "@/middlewares/multer.middleware"; @@ -9,6 +10,7 @@ import { uploadPdf } from "@/middlewares/multer.middleware"; export class NurseRoute implements Routes { public path = '/nurses' public router = Router(); + public nursesController = new NurseController(); constructor() { this.initializeRoutes(); } @@ -16,14 +18,187 @@ export class NurseRoute implements Routes { private initializeRoutes() { this.router.post( `${this.path}/signup`, + /* + #swagger.path = '/nurses/signup' + #swagger.method = 'post' + #swagger.tags = ['Nurses'] + #swagger.description = 'Creates a new nurse account. Requires national ID card upload and optional bonus file' + #swagger.consumes = ['multipart/form-data'] + + #swagger.parameters['name'] = { + in: 'formData', + description: 'name of the nurse', + required: true, + type: 'string', + } + #swagger.parameters['email'] = { + in: 'formData', + description: 'Email address', + required: true, + type: 'string', + } + #swagger.parameters['phone'] = { + in: 'formData', + description: 'Phone number', + required: true, + type: 'string', + } + #swagger.parameters['password'] = { + in: 'formData', + description: 'Initial password for the account', + required: true, + type: 'string', + } + #swagger.parameters['years_of_experience'] = { + in: 'formData', + description: 'Number of years of professional nursing experience', + required: true, + type: 'integer', + } + #swagger.parameters['gender'] = { + in: 'formData', + description: 'Gender (must match Prisma enum: MALE or FEMALE)', + required: true, + type: 'string', + } + #swagger.parameters['date_of_birth'] = { + in: 'formData', + description: 'Date of birth (format YYYY-MM-DD)', + required: true, + type: 'string', + } + #swagger.parameters['brief'] = { + in: 'formData', + description: 'Short professional summary / bio (optional)', + required: false, + type: 'string', + } + #swagger.parameters['nationalCard'] = { + in: 'formData', + description: 'National ID card or passport scan (PDF only)', + required: true, + type: 'file' + } + #swagger.parameters['bonusFile'] = { + in: 'formData', + description: 'Additional document: nursing license, experience certificate, etc.', + required: false, + type: 'file' + } + + #swagger.responses[201] = { + description: 'Account created successfully – awaiting admin approval', + schema: { + message_en: "Nurse account created successfully. Please wait for verification.", + message_ar: "تم إنشاء حساب الممرضة بنجاح. يرجى الانتظار للموافقة عليه.", + } + } + #swagger.responses[400] = { + description: 'Validation failed (missing fields, wrong file type, invalid date format, etc.)' + } + #swagger.responses[500] = { + description: 'Server error during file upload or database transaction' + } + */ + + uploadPdf.fields([ + { name: 'nationalCard', maxCount: 1 }, + { name: 'bonusFile', maxCount: 1 }, + ]), + ValidationMiddleware(NurseSignupRequestDto), + this.nursesController.nurseSignup, ) this.router.post( `${this.path}/login`, + /* + #swagger.path = '/nurses/login' + #swagger.method = 'post' + #swagger.tags = ['Nurses'] + #swagger.description = 'Authenticates nurse credentials' + #swagger.parameters['body'] = { + in: 'body', + description: 'Nurse login data', + required: true, + schema: { + $emailOrUsername: 'nurse@example.com', + $password: 'SecurePassword123', + $rememberMe: "true" + } + } + + #swagger.responses[200] = { + description: 'Login successful – approved nurse with completed profile', + schema: { + data: { + id: 'uuid-string', + name: 'Maxine Lee', + email: 'maxine.lee@example.com', + username: 'maxine.lee', + phone: '+201234567890', + gender: 'FEMALE', + nurse: { account_status: 'APPROVED' } + }, + messageEn: 'Nurse retrieved successfully', + messageAr: 'تم استرجاع بيانات الممرض بنجاح' + } + } + #swagger.responses[401] = { + description: 'Invalid credentials (wrong email/username or password)' + } + #swagger.responses[403] = { + description: 'Account not approved (PENDING or REJECTED)' + } + */ + ValidationMiddleware(NurseLoginRequestDto), + this.nursesController.nurseLogin ); this.router.patch( `${this.path}/set-password`, + /* + #swagger.path = '/nurses/set-password' + #swagger.method = 'patch' + #swagger.tags = ['Nurses'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'New password data', + required: true, + schema: { + $password: 'NewSecurePassword123' + } + } + #swagger.responses[200] = { + description: 'Password set successfully', + schema: { + messageEn: 'Password updated successfully', + messageAr: "تم تحديث كلمة المرور بنجاح" + } + } + #swagger.responses[400] = { + description: 'Password already set / validation error' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – user is not a nurse role' + } + #swagger.responses[404] = { + description: 'Nurse user not found' + } + + */ + ValidationMiddleware(NurseSetPasswordRequestDto), + AuthMiddleware, + RoleMiddleware(Role.NURSE), + this.nursesController.nurseSetPassword ); } } \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 04fcf4b..7f7fda8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,7 +8,7 @@ import { DoctorsRoute } from './routes/doctors.route'; import { ClinicRoute } from './routes/clinic.route'; import { AppointmentRoute } from './routes/appointment.route'; import { QueueRoute } from './routes/queue.route'; - +import { NurseRoute } from './routes/nurse.route'; import { UsersRoute } from './routes/user.route'; ValidateEnv(); @@ -16,7 +16,7 @@ const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute(), new QueueRoute(), - new UsersRoute() + new UsersRoute(), new NurseRoute(), ]); app.listen(); diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index fd100f5..f307c6d 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -1,4 +1,4 @@ -import { DoctorAccountStatus, PrismaClient, Role } from '@prisma/client'; +import { DoctorAccountStatus, NurseAccountStatus, PrismaClient, Role } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto } from '@/dtos/admins.dto'; @@ -231,27 +231,58 @@ export class AdminService { }); } - public async sendVerificationStatusEmail(doctorId: string, isApproved: boolean): Promise { - const doctor = await prisma.user.findUnique({ - where: { id: doctorId, role: Role.DOCTOR }, + public async sendVerificationStatusEmail(userId: string, isApproved: boolean): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId}, select: { email: true, name: true } }); - if (!doctor) { + if (!user) { const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } const mailOptions = { from: SENDER_EMAIL, - to: doctor.email, + to: user.email, subject: isApproved ? 'Doctor Account Approved - MedBridge' : 'Doctor Account Rejected - MedBridge', html: ` -

Dear Dr. ${doctor.name},

+

Dear ${user.name},

Your account has been ${isApproved ? 'approved' : 'rejected'}.

Thank you for using our platform.

-

Best regards,
MedicBridge Team

+

Best regards,
HoloCura Team

` }; await transporter.sendMail(mailOptions); } + + public async updateNurseVerificationStatus(nurseId: string, isApproved: boolean | null): Promise { + + const nurse = await prisma.user.findUnique({ + where: { id: nurseId, role: Role.NURSE }, + }); + if (!nurse) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + let accountStatus: NurseAccountStatus; + if (isApproved === true) { + accountStatus = NurseAccountStatus.APPROVED; + } else if (isApproved === false) { + accountStatus = NurseAccountStatus.REJECTED; + } else { + accountStatus = NurseAccountStatus.PENDING; + } + + await prisma.user.update({ + where: { id: nurseId }, + data: { + nurse: { + update: { + account_status: accountStatus, + } + } + } + }); + } } \ No newline at end of file diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index 41acab4..f951d1a 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -16,7 +16,7 @@ export class NurseService { private authService = new AuthService(); - public async signup(nurseData: NurseSignupRequestDto, nurseFiles: {}) { + public async nurseSignup(nurseData: NurseSignupRequestDto, nurseFiles: {}) { const existingUser = await prisma.user.findUnique({ where: { email: nurseData.email } }); @@ -37,6 +37,13 @@ export class NurseService { throw new HttpException(error.status, error.message, error.messageAr); } + const files = nurseFiles as { [key: string]: Express.Multer.File[] | undefined }; + + if (!files?.nationalCard?.length) { + const error = createBilingualError(400, ErrorMessages.NATIONAL_CARD_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + const hashedPassword = await hash(nurseData.password, 10); const createdUserId = await prisma.$transaction(async (tx) => { @@ -73,7 +80,7 @@ export class NurseService { } } - public async login(nurseLoginData: NurseLoginRequestDto): Promise<{ cookies: string[]; NurseAccountData: NurseLoginData } | boolean> { + public async nurseLogin(nurseLoginData: NurseLoginRequestDto): Promise<{ cookies: string[]; NurseAccountData: NurseLoginData } | boolean> { const nurseUserData = await prisma.user.findFirst({ where: { @@ -139,7 +146,7 @@ export class NurseService { return { cookies, NurseAccountData }; } - public async setPassword(nurseId: string, password: string): Promise { + public async nurseSetPassword(nurseId: string, password: string): Promise { const hashedPassword = await hash(password, 10); const nurseUserData = await prisma.user.findUnique({ where: { id: nurseId }, diff --git a/src/swagger-output.json b/src/swagger-output.json index 54fdd7e..e1f7965 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -43,6 +43,10 @@ { "name": "Users", "description": "User account endpoints" + }, + { + "name": "Nurses", + "description": "Nurse account endpoints" } ], "schemes": [ @@ -1773,6 +1777,69 @@ } } }, + "/admin/nurses/verify/{id}": { + "patch": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Nurse ID" + }, + { + "name": "body", + "in": "body", + "description": "Verification status", + "required": true, + "schema": { + "type": "object", + "properties": { + "isApproved": { + "type": "boolean", + "example": true + } + }, + "required": [ + "isApproved" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie or Authorization header)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurse verification status updated successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Nurse verification status updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث حالة اعتماد الممرض بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/admin/doctors/{id}": { "get": { "tags": [ @@ -6061,6 +6128,287 @@ } } } + }, + "/nurses/signup": { + "post": { + "tags": [ + "Nurses" + ], + "description": "Creates a new nurse account. Requires national ID card upload and optional bonus file", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "name", + "in": "formData", + "description": "name of the nurse", + "required": true, + "type": "string" + }, + { + "name": "email", + "in": "formData", + "description": "Email address", + "required": true, + "type": "string" + }, + { + "name": "phone", + "in": "formData", + "description": "Phone number", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "formData", + "description": "Initial password for the account", + "required": true, + "type": "string" + }, + { + "name": "years_of_experience", + "in": "formData", + "description": "Number of years of professional nursing experience", + "required": true, + "type": "integer" + }, + { + "name": "gender", + "in": "formData", + "description": "Gender (must match Prisma enum: MALE or FEMALE)", + "required": true, + "type": "string" + }, + { + "name": "date_of_birth", + "in": "formData", + "description": "Date of birth (format YYYY-MM-DD)", + "required": true, + "type": "string" + }, + { + "name": "brief", + "in": "formData", + "description": "Short professional summary / bio (optional)", + "required": false, + "type": "string" + }, + { + "name": "nationalCard", + "in": "formData", + "description": "National ID card or passport scan (PDF only)", + "required": true, + "type": "file" + }, + { + "name": "bonusFile", + "in": "formData", + "description": "Additional document: nursing license, experience certificate, etc.", + "required": false, + "type": "file" + } + ], + "responses": { + "201": { + "description": "Account created successfully – awaiting admin approval", + "schema": { + "type": "object", + "properties": { + "message_en": { + "type": "string", + "example": "Nurse account created successfully. Please wait for verification." + }, + "message_ar": { + "type": "string", + "example": "تم إنشاء حساب الممرضة بنجاح. يرجى الانتظار للموافقة عليه." + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Validation failed (missing fields, wrong file type, invalid date format, etc.)" + }, + "500": { + "description": "Server error during file upload or database transaction" + } + } + } + }, + "/nurses/login": { + "post": { + "tags": [ + "Nurses" + ], + "description": "Authenticates nurse credentials", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Nurse login data", + "required": true, + "schema": { + "type": "object", + "properties": { + "emailOrUsername": { + "type": "string", + "example": "nurse@example.com" + }, + "password": { + "type": "string", + "example": "SecurePassword123" + }, + "rememberMe": { + "type": "string", + "example": "true" + } + }, + "required": [ + "emailOrUsername", + "password", + "rememberMe" + ] + } + } + ], + "responses": { + "200": { + "description": "Login successful – approved nurse with completed profile", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Maxine Lee" + }, + "email": { + "type": "string", + "example": "maxine.lee@example.com" + }, + "username": { + "type": "string", + "example": "maxine.lee" + }, + "phone": { + "type": "string", + "example": "+201234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرض بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Invalid credentials (wrong email/username or password)" + }, + "403": { + "description": "Account not approved (PENDING or REJECTED)" + } + } + } + }, + "/nurses/set-password": { + "patch": { + "tags": [ + "Nurses" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "New password data", + "required": true, + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "NewSecurePassword123" + } + }, + "required": [ + "password" + ] + } + } + ], + "responses": { + "200": { + "description": "Password set successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Password updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث كلمة المرور بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Password already set / validation error" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – user is not a nurse role" + }, + "404": { + "description": "Nurse user not found" + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.mjs b/src/swagger.mjs index b2ef982..2518576 100644 --- a/src/swagger.mjs +++ b/src/swagger.mjs @@ -16,9 +16,8 @@ const doc = { { name: 'Clinics', description: 'Clinic endpoints' }, { name: 'Appointments', description: 'Appointment endpoints' }, { name: 'Queue', description: 'Queue endpoints' }, - - { name: 'Users', description: 'User account endpoints' }, + { name: 'Nurses', description: 'Nurse account endpoints' }, ], }; @@ -26,6 +25,6 @@ const doc = { const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' - , './src/routes/appointment.route.ts', './src/routes/queue.route.ts', './src/routes/user.route.ts']; + , './src/routes/appointment.route.ts', './src/routes/queue.route.ts', './src/routes/user.route.ts', './src/routes/nurse.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 2bd0f08..61695e1 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -116,6 +116,10 @@ export const ErrorMessages = { en: 'Nurse account is not approved yet', ar: 'حساب الممرضة غير مفعل بعد', }, + NATIONAL_CARD_REQUIRED: { + en: 'National card image is required', + ar: 'صورة البطاقة الوطنية مطلوبة', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index eecf33f..854d93b 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -124,6 +124,10 @@ export const SuccessResponseMessages = { message_en: 'Password set successfully by nurse', message_ar: 'تم تعيين كلمة المرور بنجاح من قبل الممرضة', }, + NURSE_VERIFICATION_STATUS_UPDATED: { + message_en: "Nurse verification status updated successfully.", + message_ar: "تم تحديث حالة اعتماد الممرضة بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From 4183c0507e7f1ad6bfede3b61000110421d75983 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 17 Feb 2026 23:19:13 +0200 Subject: [PATCH 154/210] add announcements by doctor --- src/controllers/doctor.controller.ts | 12 +++- src/dtos/doctors.dto.ts | 51 +++++++++++++-- src/routes/doctors.route.ts | 52 ++++++++++++++- src/services/doctor.service.ts | 54 +++++++++++++++- src/swagger-output.json | 97 ++++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 ++ 6 files changed, 263 insertions(+), 7 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 444bf0b..8f35e31 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,5 +1,5 @@ -import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; import { RequestWithUser } from "@/interfaces"; import { DoctorService } from "@/services/doctor.service"; import { UserService } from "@/services/user.service"; @@ -76,4 +76,14 @@ export class DoctorController { ...response }); } + + public postAnnouncement = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const announcementData: PostAnnouncementDto = req.body; + + await this.doctorService.postAnnouncement(doctorId, announcementData); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENT_CREATED_SUCCESSFULLY); + res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index eac66ff..a93ac25 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -1,8 +1,8 @@ -import { TransformSpecialization } from "@/utils/specializationTransform"; -import { IsValidSpecialization } from "@/validators/specialization.validator"; -import { AvailabilityType, Gender } from "@prisma/client"; -import { IsString, IsNotEmpty, IsEmail } from "class-validator"; +import { AvailabilityType, DayOfWeek, Gender } from "@prisma/client"; +import { IsString, IsNotEmpty, IsEmail, IsArray, IsOptional, IsInt, IsEnum, ValidateNested, Min } from "class-validator"; import { UpdateUserProfileDto } from "./users.dto"; +import { Type } from "class-transformer"; + export class DoctorSignupRequestDto { @IsString() @@ -64,4 +64,47 @@ export class DoctorProfilePictureRequestDto { export class DoctorUpdateProfileRequestDto extends UpdateUserProfileDto { @IsString() availability_type?: AvailabilityType; +} + +export class WorkingDayDto { + @IsEnum(DayOfWeek) + @IsNotEmpty() + public day_of_week: DayOfWeek; + + @IsString() + @IsNotEmpty() + public start_time: string; + + @IsString() + @IsNotEmpty() + public end_time: string; +} + +export class PostAnnouncementDto { + @IsString() + @IsNotEmpty() + public clinic_id: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => WorkingDayDto) + public working_days: WorkingDayDto[]; + + @IsOptional() + @IsEnum(Gender) + public gender?: Gender; + + @IsOptional() + @IsInt() + @Min(0) + public max_age?: number; + + @IsOptional() + @IsInt() + @Min(0) + public years_of_experience?: number; + + @IsOptional() + @IsString() + public notes?: string; } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index a05da21..e6d26d4 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,5 +1,5 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; @@ -189,5 +189,55 @@ export class DoctorsRoute implements Routes { RoleMiddleware(Role.DOCTOR), errorWrapper(this.doctorsController.doctorSetPassword) ); + + this.router.post( + `/doctors/announcement`, + /* + #swagger.path = '/doctors/announcement' + #swagger.method = 'post' + #swagger.tags = ['Doctors'] + #swagger.description = 'Allows doctor to post a nurse hiring announcement' + #swagger.parameters['body'] = { + in: 'body', + description: 'Announcement data', + required: true, + schema: { + $clinic_id: 'uuid-of-the-clinic', + $working_days: [ + { + $day_of_week: 'MONDAY', + $start_time: '09:00', + $end_time: '17:00' + }, + { + $day_of_week: 'TUESDAY', + $start_time: '10:00', + $end_time: '17:00' + } + ], + gender: 'FEMALE', + max_age: 40, + years_of_experience: 3, + notes: 'Looking for an experienced nurse' + } + } + #swagger.responses[201] = { + description: 'Announcement posted successfully', + schema: { + messageEn: 'Announcement created successfully', + messageAr: 'تم نشر الإعلان بنجاح' + } + } + #swagger.responses[403] = { + description: 'Doctor account not approved (PENDING or REJECTED)' + } + #swagger.responses[404] = { + description: 'Doctor not found or does not belong to the specified clinic' + } + */ + AuthMiddleware, + ValidationMiddleware(PostAnnouncementDto), + this.doctorsController.postAnnouncement + ) } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 843614a..5abeedf 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -1,4 +1,4 @@ -import { DoctorLoginRequestDto, DoctorSignupRequestDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; @@ -400,6 +400,58 @@ export class DoctorService { return doctorPersonalData; } + public async postAnnouncement(doctorId: string, data: PostAnnouncementDto): Promise { + const doctor = await prisma.doctor.findUnique({ + where: { + id: doctorId + }, + select: { + account_status: true + } + }); + + if (!doctor) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (doctor.account_status !== DoctorAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const clinicDoctor = await prisma.clinicDoctor.findUnique({ + where: { + clinic_id_doctor_id: { + clinic_id: data.clinic_id, + doctor_id: doctorId + } + } + }); + + if (!clinicDoctor) { + const error = createBilingualError(404, ErrorMessages.DOCTOR_NOT_ASSOCIATED_WITH_CLINIC); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.announcement.create({ + data: { + doctor_id: doctorId, + clinic_id: data.clinic_id, + gender: data.gender, + max_age: data.max_age, + years_of_experience: data.years_of_experience, + notes: data.notes, + working_days: { + create: data.working_days.map(day => ({ + day_of_week: day.day_of_week, + start_time: day.start_time, + end_time: day.end_time + })) + } + } + }); + } } diff --git a/src/swagger-output.json b/src/swagger-output.json index e1f7965..1af6302 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3376,6 +3376,103 @@ } } }, + "/doctors/announcement": { + "post": { + "tags": [ + "Doctors" + ], + "description": "Allows doctor to post a nurse hiring announcement", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Announcement data", + "required": true, + "schema": { + "type": "object", + "properties": { + "clinic_id": { + "type": "string", + "example": "uuid-of-the-clinic" + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "TUESDAY" + }, + "start_time": { + "type": "string", + "example": "10:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + }, + "required": [ + "day_of_week", + "start_time", + "end_time" + ] + } + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "max_age": { + "type": "number", + "example": 40 + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "notes": { + "type": "string", + "example": "Looking for an experienced nurse" + } + }, + "required": [ + "clinic_id", + "working_days" + ] + } + } + ], + "responses": { + "201": { + "description": "Announcement posted successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Announcement created successfully" + }, + "messageAr": { + "type": "string", + "example": "تم نشر الإعلان بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "403": { + "description": "Doctor account not approved (PENDING or REJECTED)" + }, + "404": { + "description": "Doctor not found or does not belong to the specified clinic" + } + } + } + }, "/clinics": { "post": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 854d93b..a357067 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -110,6 +110,10 @@ export const SuccessResponseMessages = { message_en: "Password set successfully.", message_ar: "تم تعيين كلمة المرور بنجاح.", }, + ANNOUNCEMENT_CREATED_SUCCESSFULLY: { + message_en: "Announcement created successfully.", + message_ar: "تم إنشاء الإعلان بنجاح.", + }, // Success messages for nurses NURSE_CREATED_WAITING_VERIFICATION: { From 82826ccf344c68cc43ca8b509b94413e94c190e9 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 18 Feb 2026 00:33:06 +0200 Subject: [PATCH 155/210] get doctors announcements --- src/controllers/doctor.controller.ts | 11 +++ src/interfaces/doctors.interface.ts | 30 +++++- src/routes/doctors.route.ts | 62 ++++++++++++ src/services/doctor.service.ts | 104 ++++++++++++++++++-- src/swagger-output.json | 138 +++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 + 6 files changed, 340 insertions(+), 9 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 8f35e31..1042b34 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -86,4 +86,15 @@ export class DoctorController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENT_CREATED_SUCCESSFULLY); res.status(201).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } + + public getDoctorAnnouncements = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const announcements = await this.doctorService.getDoctorAnnouncements(doctorId); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENTS_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: announcements, + ...responseMessage + }); + } } \ No newline at end of file diff --git a/src/interfaces/doctors.interface.ts b/src/interfaces/doctors.interface.ts index d11a52c..07b5724 100644 --- a/src/interfaces/doctors.interface.ts +++ b/src/interfaces/doctors.interface.ts @@ -1,4 +1,4 @@ -import { DoctorAccountStatus, AvailabilityType, Gender} from "@prisma/client"; +import { DoctorAccountStatus, AvailabilityType, Gender, AnnouncementStatus, DayOfWeek} from "@prisma/client"; import { DoctorClinics } from "./clinics.interface"; export interface Doctor { @@ -34,4 +34,32 @@ export interface DoctorPersonalData { profilePic: string; is_online: boolean; clinics?: DoctorClinics[] +} + +export interface WorkingDays { + day_of_week: DayOfWeek; + start_time: string; + end_time: string; +} + +export interface DoctorAnnouncements { + id: string; + doctor: { + id: string; + name: string; + gender: Gender; + profilePic: string; + }; + clinic: { + id: string; + name: string + address: string; + address_maps_link: string; + }; + working_days: WorkingDays[]; + status?: AnnouncementStatus; + gender?: Gender; + max_age?: number; + years_of_experience?: number; + notes?: string; } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index e6d26d4..02d8016 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -239,5 +239,67 @@ export class DoctorsRoute implements Routes { ValidationMiddleware(PostAnnouncementDto), this.doctorsController.postAnnouncement ) + + this.router.get( + `/doctors/announcements`, + /* + #swagger.path = '/doctors/announcements' + #swagger.method = 'get' + #swagger.tags = ['Doctors'] + #swagger.description = 'Retrieves all nurse hiring announcements posted by the doctor' + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Announcements retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + doctor: { + id: 'uuid-string', + name: 'Dr. Ahmed Ali', + gender: 'MALE', + profilePic: 'https://res.cloudinary.com/example/image.jpg' + }, + clinic: { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...' + }, + working_days: [ + { + day_of_week: 'MONDAY', + start_time: '09:00', + end_time: '17:00' + } + ], + status: 'PENDING', + gender: 'FEMALE', + max_age: 40, + years_of_experience: 3, + notes: 'Looking for an experienced nurse' + } + ], + messageEn: 'Announcements retrieved successfully', + messageAr: 'تم استرجاع الإعلانات بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Doctor not found' + } + */ + AuthMiddleware, + this.doctorsController.getDoctorAnnouncements + + ) + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 5abeedf..1bd63f1 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -4,7 +4,7 @@ import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { Doctor, DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; -import { DoctorLoginData, DoctorPersonalData } from "@/interfaces/doctors.interface"; +import { DoctorLoginData, DoctorPersonalData, DoctorAnnouncements } from "@/interfaces/doctors.interface"; import { AuthService } from "./auth.service"; import prisma from "@/config/prisma"; import cloudinary from "@/utils/cloudinary"; @@ -266,7 +266,7 @@ export class DoctorService { } } - public async getDoctors(lang: 'en' | 'ar', gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean ): Promise { + public async getDoctors(lang: 'en' | 'ar', gender?: string, minFees?: number, maxFees?: number, isOnline?: boolean): Promise { const WhereClause: any = { is_accepting: true, doctor: { @@ -318,7 +318,7 @@ export class DoctorService { phone: true, date_of_birth: true, photo_url: true, - + }, }, }, @@ -361,7 +361,7 @@ export class DoctorService { const age = await this.userService.calculateUserAge(user.date_of_birth); let canWorkOnline = false; if (doctor.availability_type == 'ONLINE' || doctor.availability_type == 'BOTH') { - canWorkOnline = true; + canWorkOnline = true; } const allClinics: DoctorClinics[] = []; @@ -400,13 +400,101 @@ export class DoctorService { return doctorPersonalData; } + public async getDoctorAnnouncements(doctorId: string): Promise { + const doctor = await prisma.doctor.findUnique({ + where: { + id: doctorId + }, + select: { + account_status: true + } + }); + + if (!doctor) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (doctor.account_status !== DoctorAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const announcements = await prisma.announcement.findMany({ + where: { + doctor_id: doctorId + }, + select: { + id: true, + doctor: { + select: { + user: { + select: { + id: true, + name: true, + gender: true, + photo_url: true, + } + } + } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + }, + working_days: { + select: { + day_of_week: true, + start_time: true, + end_time: true, + } + }, + status: true, + gender: true, + max_age: true, + years_of_experience: true, + notes: true, + + } + }); + return announcements.map(announcement => ({ + id: announcement.id, + doctor: { + id: announcement.doctor.user.id, + name: announcement.doctor.user.name, + gender: announcement.doctor.user.gender, + profilePic: announcement.doctor.user.photo_url, + }, + clinic: { + id: announcement.clinic.id, + name: announcement.clinic.name, + address: announcement.clinic.address, + address_maps_link: announcement.clinic.address_maps_link, + }, + working_days: announcement.working_days.map(wd => ({ + day_of_week: wd.day_of_week, + start_time: wd.start_time, + end_time: wd.end_time, + })), + status: announcement.status, + gender: announcement.gender || undefined, + max_age: announcement.max_age || undefined, + years_of_experience: announcement.years_of_experience || undefined, + notes: announcement.notes || undefined, + })); + } + public async postAnnouncement(doctorId: string, data: PostAnnouncementDto): Promise { const doctor = await prisma.doctor.findUnique({ - where: { - id: doctorId + where: { + id: doctorId }, - select: { - account_status: true + select: { + account_status: true } }); diff --git a/src/swagger-output.json b/src/swagger-output.json index 1af6302..aa7c226 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3473,6 +3473,144 @@ } } }, + "/doctors/announcements": { + "get": { + "tags": [ + "Doctors" + ], + "description": "Retrieves all nurse hiring announcements posted by the doctor", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Announcements retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "doctor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Dr. Ahmed Ali" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/image.jpg" + } + } + }, + "clinic": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Al Salam Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + } + } + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "MONDAY" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + } + } + }, + "status": { + "type": "string", + "example": "PENDING" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "max_age": { + "type": "number", + "example": 40 + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "notes": { + "type": "string", + "example": "Looking for an experienced nurse" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Announcements retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الإعلانات بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Doctor not found" + } + } + } + }, "/clinics": { "post": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index a357067..4ea8029 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -114,6 +114,10 @@ export const SuccessResponseMessages = { message_en: "Announcement created successfully.", message_ar: "تم إنشاء الإعلان بنجاح.", }, + ANNOUNCEMENTS_RETRIEVED_SUCCESSFULLY: { + message_en: "Announcements retrieved successfully.", + message_ar: "تم استرجاع الإعلانات بنجاح.", + }, // Success messages for nurses NURSE_CREATED_WAITING_VERIFICATION: { From 1b2077c8c2f6ef529bb0778bd90df13d4d43088a Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 18 Feb 2026 20:53:51 +0200 Subject: [PATCH 156/210] get all applications of a specific announcement --- src/controllers/doctor.controller.ts | 12 +++ src/interfaces/nurse.interface.ts | 15 +++- src/routes/doctors.route.ts | 55 ++++++++++++++ src/services/doctor.service.ts | 66 ++++++++++++++++- src/swagger-output.json | 106 +++++++++++++++++++++++++++ src/utils/errorMessages.ts | 8 ++ src/utils/responseMessages.ts | 4 + 7 files changed, 264 insertions(+), 2 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 1042b34..0b0f70f 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -97,4 +97,16 @@ export class DoctorController { ...responseMessage }); } + + public getAnnouncementApplicants = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const { announcementId } = req.params; + const applicants = await this.doctorService.getAnnouncementApplicants(doctorId, announcementId); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLICANTS_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: applicants, + ...responseMessage + }); + } } \ No newline at end of file diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts index 6ad303d..e2425c8 100644 --- a/src/interfaces/nurse.interface.ts +++ b/src/interfaces/nurse.interface.ts @@ -1,4 +1,4 @@ -import { NurseAccountStatus} from "@prisma/client"; +import { NurseAccountStatus, Gender} from "@prisma/client"; export interface NurseLoginData { id: string, @@ -12,3 +12,16 @@ export interface NurseLoginData { } } +export interface NurseData { + id: string; + name: string; + email: string; + phone: string; + gender: Gender; + age: number; + profilePic: string | null; + years_of_experience: number; + nationalCardUrl: string; + brief: string | null; + bonusFileUrl: string | null; +} \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 02d8016..1b10a8a 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -301,5 +301,60 @@ export class DoctorsRoute implements Routes { ) + this.router.get( + `/doctors/announcements/:announcementId/applicants`, + /* + #swagger.path = '/doctors/announcements/{announcementId}/applicants' + #swagger.method = 'get' + #swagger.tags = ['Doctors'] + #swagger.description = 'Retrieves all PENDING nurse applicants for a specific announcement' + #swagger.parameters['announcementId'] = { + in: 'path', + description: 'ID of the announcement to retrieve applicants for', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Applicants retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + name: 'Max Mustermann', + email: 'max.mustermann@example.com', + gender: 'FEMALE', + phone: '+201234567890', + age: 28, + profilePic: 'https://res.cloudinary.com/example/photo.jpg', + years_of_experience: 5, + nationalCardUrl: 'https://res.cloudinary.com/example/national_card.pdf', + brief: 'Experienced ICU nurse with 5 years in critical care', + bonusFileUrl: 'https://res.cloudinary.com/example/bonus.pdf' + } + ], + messageEn: 'Applicants retrieved successfully', + messageAr: 'تم استرجاع المتقدمين بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – announcement does not belong to this doctor' + } + #swagger.responses[404] = { + description: 'Announcement not found' + } + */ + AuthMiddleware, + this.doctorsController.getAnnouncementApplicants + ) + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 1bd63f1..a615530 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -5,6 +5,7 @@ import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { Doctor, DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; import { DoctorLoginData, DoctorPersonalData, DoctorAnnouncements } from "@/interfaces/doctors.interface"; +import { NurseData } from "@/interfaces/nurse.interface"; import { AuthService } from "./auth.service"; import prisma from "@/config/prisma"; import cloudinary from "@/utils/cloudinary"; @@ -419,7 +420,7 @@ export class DoctorService { const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); throw new HttpException(error.status, error.message, error.messageAr); } - + const announcements = await prisma.announcement.findMany({ where: { doctor_id: doctorId @@ -488,6 +489,69 @@ export class DoctorService { })); } + public async getAnnouncementApplicants(doctorId: string, announcementId: string): Promise { + const announcement = await prisma.announcement.findUnique({ + where: { + id: announcementId + }, + select: { + doctor_id: true + } + }); + + if (!announcement) { + const error = createBilingualError(404, ErrorMessages.ANNOUNCEMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (announcement.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const applicants = await prisma.announcementNurse.findMany({ + where: { + announcement_id: announcementId, + status: 'PENDING' + }, + select: { + nurse: { + select: { + years_of_experience: true, + nationalCardUrl: true, + bonusFileUrl: true, + brief: true, + user: { + select: { + id: true, + name: true, + email: true, + gender: true, + phone: true, + date_of_birth: true, + photo_url: true, + } + } + } + } + } + }); + + return Promise.all(applicants.map(async ({ nurse }) => ({ + id: nurse.user.id, + name: nurse.user.name, + email: nurse.user.email, + gender: nurse.user.gender, + phone: nurse.user.phone, + age: await this.userService.calculateUserAge(nurse.user.date_of_birth), + profilePic: nurse.user.photo_url, + years_of_experience: nurse.years_of_experience, + nationalCardUrl: nurse.nationalCardUrl, + bonusFileUrl: nurse.bonusFileUrl, + brief: nurse.brief, + }))); + } + public async postAnnouncement(doctorId: string, data: PostAnnouncementDto): Promise { const doctor = await prisma.doctor.findUnique({ where: { diff --git a/src/swagger-output.json b/src/swagger-output.json index aa7c226..07c781c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3611,6 +3611,112 @@ } } }, + "/doctors/announcements/{announcementId}/applicants": { + "get": { + "tags": [ + "Doctors" + ], + "description": "Retrieves all PENDING nurse applicants for a specific announcement", + "parameters": [ + { + "name": "announcementId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the announcement to retrieve applicants for" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Applicants retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Max Mustermann" + }, + "email": { + "type": "string", + "example": "max.mustermann@example.com" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "phone": { + "type": "string", + "example": "+201234567890" + }, + "age": { + "type": "number", + "example": 28 + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/photo.jpg" + }, + "years_of_experience": { + "type": "number", + "example": 5 + }, + "nationalCardUrl": { + "type": "string", + "example": "https://res.cloudinary.com/example/national_card.pdf" + }, + "brief": { + "type": "string", + "example": "Experienced ICU nurse with 5 years in critical care" + }, + "bonusFileUrl": { + "type": "string", + "example": "https://res.cloudinary.com/example/bonus.pdf" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Applicants retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع المتقدمين بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – announcement does not belong to this doctor" + }, + "404": { + "description": "Announcement not found" + } + } + } + }, "/clinics": { "post": { "tags": [ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 61695e1..849a4c3 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -106,6 +106,14 @@ export const ErrorMessages = { en: 'Maximum number of created clinics reached', ar: 'تم الوصول إلى الحد الأقصى لعدد العيادات', }, + ANNOUNCEMENT_NOT_FOUND: { + en: 'Announcement not found', + ar: 'الإعلان غير موجود', + }, + UNAUTHORIZED_ACCESS :{ + en: 'You are not authorized to access this', + ar: 'ليس لديك صلاحية للوصول إلى هذا ', + }, // nurse NURSE_PASSWORD_ALREADY_SET: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 4ea8029..13c9e51 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -118,6 +118,10 @@ export const SuccessResponseMessages = { message_en: "Announcements retrieved successfully.", message_ar: "تم استرجاع الإعلانات بنجاح.", }, + APPLICANTS_RETRIEVED_SUCCESSFULLY: { + message_en: "Announcement applicants retrieved successfully.", + message_ar: "تم استرجاع المتقدمين للإعلان بنجاح.", + }, // Success messages for nurses NURSE_CREATED_WAITING_VERIFICATION: { From cf0d527ca67b76177fa7acad53f73fb373d64c19 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 23 Feb 2026 21:47:10 +0200 Subject: [PATCH 157/210] get active announcements for the nurse --- src/controllers/nurse.controller.ts | 12 +++ src/interfaces/nurse.interface.ts | 2 +- src/routes/nurse.route.ts | 62 +++++++++++++ src/services/nurse.service.ts | 88 ++++++++++++++++++ src/swagger-output.json | 138 ++++++++++++++++++++++++++++ src/utils/errorMessages.ts | 4 + src/utils/responseMessages.ts | 4 + 7 files changed, 309 insertions(+), 1 deletion(-) diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts index 0f36541..da52d3b 100644 --- a/src/controllers/nurse.controller.ts +++ b/src/controllers/nurse.controller.ts @@ -8,6 +8,7 @@ import { NurseSignupRequestDto, NurseLoginRequestDto, NurseSetPasswordRequestDto import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; + export class NurseController { private nurseService = Container.get(NurseService); @@ -45,4 +46,15 @@ export class NurseController { res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); + public getAllAnnouncements = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { + const nurseId = req.user?.id; + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const announcements = await this.nurseService.getAllAnnouncements(nurseId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENTS_RETRIEVED); + res.status(200).json({ data: announcements, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); + } \ No newline at end of file diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts index e2425c8..a6036ce 100644 --- a/src/interfaces/nurse.interface.ts +++ b/src/interfaces/nurse.interface.ts @@ -1,4 +1,4 @@ -import { NurseAccountStatus, Gender} from "@prisma/client"; +import { NurseAccountStatus, Gender, AnnouncementStatus} from "@prisma/client"; export interface NurseLoginData { id: string, diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index b6f2c09..ec278aa 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -200,5 +200,67 @@ export class NurseRoute implements Routes { RoleMiddleware(Role.NURSE), this.nursesController.nurseSetPassword ); + + this.router.get( + `${this.path}/announcements`, + /* + #swagger.path = '/nurses/announcements' + #swagger.method = 'get' + #swagger.tags = ['Nurses'] + #swagger.description = 'Retrieves all active announcements for the nurse' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Announcements retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + doctor: { + id: 'uuid-string', + name: 'Dr. House', + gender: 'MALE', + profilePic: 'https://res.cloudinary.com/example/image.jpg' + }, + clinic: { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...' + }, + working_days: [ + { + day_of_week: 'MONDAY', + start_time: '09:00', + end_time: '17:00' + } + ], + status: 'PENDING', + gender: 'FEMALE', + max_age: 40, + years_of_experience: 3, + notes: 'Looking for an experienced nurse' + } + ], + messageEn: 'Announcements retrieved successfully', + messageAr: 'تم استرجاع الإعلانات بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Nurse account not approved (PENDING or REJECTED)' + } + */ + AuthMiddleware, + this.nursesController.getAllAnnouncements + ); } } \ No newline at end of file diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index f951d1a..ce13101 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -10,6 +10,7 @@ import prisma from '@/config/prisma'; import { Role, NurseAccountStatus } from "@prisma/client"; import cloudinary from "@/utils/cloudinary"; import fs from "fs"; +import { DoctorAnnouncements } from "@/interfaces/doctors.interface"; @Service() export class NurseService { @@ -169,6 +170,93 @@ export class NurseService { }); } + public async getAllAnnouncements(nurseId): Promise { + const nurseData = await prisma.nurse.findUnique({ + where: { id: nurseId }, + select: { account_status: true } + }); + + if (nurseData.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(404, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + const announcements = await prisma.announcement.findMany({ + where: { + deleted_at: null, + status: { + in: ['POSTED', 'PENDING'] + } + }, + select: { + id: true, + doctor: { + select: { + user: { + select: { + id: true, + name: true, + gender: true, + photo_url: true, + } + } + } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + }, + working_days: { + select: { + day_of_week: true, + start_time: true, + end_time: true, + } + }, + status: true, + gender: true, + max_age: true, + years_of_experience: true, + notes: true, + + } + }); + + if (!announcements) { + return []; + } + + return announcements.map(announcement => ({ + id: announcement.id, + doctor: { + id: announcement.doctor.user.id, + name: announcement.doctor.user.name, + gender: announcement.doctor.user.gender, + profilePic: announcement.doctor.user.photo_url, + }, + clinic: { + id: announcement.clinic.id, + name: announcement.clinic.name, + address: announcement.clinic.address, + address_maps_link: announcement.clinic.address_maps_link, + }, + working_days: announcement.working_days.map(wd => ({ + day_of_week: wd.day_of_week, + start_time: wd.start_time, + end_time: wd.end_time, + })), + status: announcement.status, + gender: announcement.gender || undefined, + max_age: announcement.max_age || undefined, + years_of_experience: announcement.years_of_experience || undefined, + notes: announcement.notes || undefined, + })); + + } + private async _uploadFiles(files: Express.Multer.File[], nurseId: string): Promise { const uploadedFiles: { public_id: string }[] = []; diff --git a/src/swagger-output.json b/src/swagger-output.json index 07c781c..c21254c 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -6750,6 +6750,144 @@ } } } + }, + "/nurses/announcements": { + "get": { + "tags": [ + "Nurses" + ], + "description": "Retrieves all active announcements for the nurse", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Announcements retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "doctor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Dr. House" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/image.jpg" + } + } + }, + "clinic": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Al Salam Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + } + } + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "MONDAY" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + } + } + }, + "status": { + "type": "string", + "example": "PENDING" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "max_age": { + "type": "number", + "example": 40 + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "notes": { + "type": "string", + "example": "Looking for an experienced nurse" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Announcements retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الإعلانات بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Nurse account not approved (PENDING or REJECTED)" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 849a4c3..a59b2ec 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -128,6 +128,10 @@ export const ErrorMessages = { en: 'National card image is required', ar: 'صورة البطاقة الوطنية مطلوبة', }, + NURSE_ID_NOT_FOUND: { + en: 'Nurse ID not found in request', + ar: 'معرف الممرضة غير موجود في الطلب', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 13c9e51..0b33766 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -122,6 +122,10 @@ export const SuccessResponseMessages = { message_en: "Announcement applicants retrieved successfully.", message_ar: "تم استرجاع المتقدمين للإعلان بنجاح.", }, + ANNOUNCEMENTS_RETRIEVED: { + message_en: "Announcements retrieved successfully.", + message_ar: "تم استرجاع الإعلانات بنجاح.", + }, // Success messages for nurses NURSE_CREATED_WAITING_VERIFICATION: { From 7bbcc93ba3e22fee573158bb5e056e724e4b73a4 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 23 Feb 2026 22:36:40 +0200 Subject: [PATCH 158/210] apply to a specific announcement --- src/controllers/nurse.controller.ts | 11 +++++++ src/routes/nurse.route.ts | 42 +++++++++++++++++++++++++ src/services/nurse.service.ts | 48 +++++++++++++++++++++++++++++ src/utils/errorMessages.ts | 8 +++++ src/utils/responseMessages.ts | 4 +++ 5 files changed, 113 insertions(+) diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts index da52d3b..254c2e3 100644 --- a/src/controllers/nurse.controller.ts +++ b/src/controllers/nurse.controller.ts @@ -57,4 +57,15 @@ export class NurseController { res.status(200).json({ data: announcements, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); + public applyToAnnouncement = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { + const nurseId = req.user?.id; + const announcementId = req.params.announcementId; + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.nurseService.applyToAnnouncement(nurseId, announcementId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLIED_TO_ANNOUNCEMENT_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); } \ No newline at end of file diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index ec278aa..337619b 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -262,5 +262,47 @@ export class NurseRoute implements Routes { AuthMiddleware, this.nursesController.getAllAnnouncements ); + + this.router.post( + `${this.path}/announcements/:announcementId/apply`, + /* + #swagger.path = '/nurses/announcements/{announcementId}/apply' + #swagger.method = 'post' + #swagger.tags = ['Nurses'] + #swagger.description = 'Apply to a specific announcement' + + #swagger.parameters['announcementId'] = { + in: 'path', + description: 'ID of the announcement to apply for', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Application submitted successfully', + schema: { + messageEn: 'Applied to announcement successfully', + messageAr: 'تم التقديم على الإعلان بنجاح' + } + } + #swagger.responses[400] = { + description: 'Invalid announcement ID / already applied / validation error' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Announcement not found / Nurse account not approved (PENDING or REJECTED)' + } + */ + AuthMiddleware, + this.nursesController.applyToAnnouncement + ) } } \ No newline at end of file diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index ce13101..00e01fb 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -170,6 +170,54 @@ export class NurseService { }); } + public async applyToAnnouncement(nurseId: string, announcementId: string): Promise { + const nurseData = await prisma.nurse.findUnique({ + where: { id: nurseId }, + select: { account_status: true } + }); + + if (nurseData.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(404, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const announcement = await prisma.announcement.findUnique({ + where: { + id: announcementId, + }, + select: { + status: true, + } + }); + + if (announcement.status == 'EXPIRED') { + const error = createBilingualError(404, ErrorMessages.ANNOUNCEMENT_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const existingApplication = await prisma.announcementNurse.findUnique({ + where: { + announcement_id_nurse_id: { + nurse_id: nurseId, + announcement_id: announcementId + } + } + }); + + if (existingApplication) { + const error = createBilingualError(409, ErrorMessages.APPLICATION_ALREADY_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.announcementNurse.create({ + data: { + nurse_id: nurseId, + announcement_id: announcementId, + status: 'PENDING' + } + }); + } + public async getAllAnnouncements(nurseId): Promise { const nurseData = await prisma.nurse.findUnique({ where: { id: nurseId }, diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index a59b2ec..5a80a3c 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -132,6 +132,14 @@ export const ErrorMessages = { en: 'Nurse ID not found in request', ar: 'معرف الممرضة غير موجود في الطلب', }, + ANNOUNCEMENT_EXPIRED: { + en: 'Announcement has expired', + ar: 'انتهت صلاحية الإعلان', + }, + APPLICATION_ALREADY_EXISTS: { + en: 'You have already applied to this announcement', + ar: 'لقد تقدمت بالفعل لهذا الإعلان', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 0b33766..5e5c3bd 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -144,6 +144,10 @@ export const SuccessResponseMessages = { message_en: "Nurse verification status updated successfully.", message_ar: "تم تحديث حالة اعتماد الممرضة بنجاح.", }, + APPLIED_TO_ANNOUNCEMENT_SUCCESSFULLY: { + message_en: "Applied to announcement successfully.", + message_ar: "تم التقديم للإعلان بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From 566798d68ad4aca7608884f1a555c852f49ebba7 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 24 Feb 2026 02:43:00 +0200 Subject: [PATCH 159/210] approve/reject applicants by doctor --- src/controllers/doctor.controller.ts | 33 +++++ src/routes/doctors.route.ts | 96 ++++++++++++++- src/services/doctor.service.ts | 137 +++++++++++++++++++++ src/swagger-output.json | 176 +++++++++++++++++++++++++++ src/utils/errorMessages.ts | 12 ++ src/utils/responseMessages.ts | 8 ++ 6 files changed, 460 insertions(+), 2 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 0b0f70f..5256392 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -100,6 +100,10 @@ export class DoctorController { public getAnnouncementApplicants = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const doctorId = req.user?.id; + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } const { announcementId } = req.params; const applicants = await this.doctorService.getAnnouncementApplicants(doctorId, announcementId); @@ -109,4 +113,33 @@ export class DoctorController { ...responseMessage }); } + + public approveApplicant = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const { applicantId } = req.params; + const { announcementId } = req.query; + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.doctorService.approveApplicant(announcementId as string, applicantId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLICANT_APPROVED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } + + public rejectApplicant = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const { applicantId } = req.params; + const { announcementId } = req.query; + + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + await this.doctorService.rejectApplicant(announcementId as string, applicantId); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLICANT_REJECTED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 1b10a8a..a5f95cb 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -296,8 +296,8 @@ export class DoctorsRoute implements Routes { description: 'Doctor not found' } */ - AuthMiddleware, - this.doctorsController.getDoctorAnnouncements + AuthMiddleware, + this.doctorsController.getDoctorAnnouncements ) @@ -356,5 +356,97 @@ export class DoctorsRoute implements Routes { this.doctorsController.getAnnouncementApplicants ) + this.router.patch( + `/doctors/announcements/:applicantId/approve`, + /* + #swagger.path = '/doctors/announcements/{applicantId}/approve' + #swagger.method = 'patch' + #swagger.tags = ['Doctors'] + #swagger.description = 'Approves a nurse applicant for a specific announcement' + #swagger.parameters['applicantId'] = { + in: 'path', + description: 'ID of the nurse applicant to approve', + required: true, + type: 'string' + } + #swagger.parameters['announcementId'] = { + in: 'query', + description: 'ID of the announcement the applicant applied to', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Applicant approved successfully', + schema: { + messageEn: 'Applicant approved successfully', + messageAr: 'تم قبول المتقدم بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – announcement does not belong to this doctor' + } + #swagger.responses[404] = { + description: 'Applicant or announcement not found' + } + */ + AuthMiddleware, + this.doctorsController.approveApplicant + ) + + this.router.patch( + `/doctors/announcements/:applicantId/reject`, + /* + #swagger.path = '/doctors/announcements/{applicantId}/reject' + #swagger.method = 'patch' + #swagger.tags = ['Doctors'] + #swagger.description = 'Rejects a nurse applicant for a specific announcement' + #swagger.parameters['applicantId'] = { + in: 'path', + description: 'ID of the nurse applicant to reject', + required: true, + type: 'string' + } + #swagger.parameters['announcementId'] = { + in: 'query', + description: 'ID of the announcement the applicant applied to', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Applicant rejected successfully', + schema: { + messageEn: 'Applicant rejected successfully', + messageAr: 'تم رفض المتقدم بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – announcement does not belong to this doctor' + } + #swagger.responses[404] = { + description: 'Applicant or announcement not found' + } + */ + AuthMiddleware, + this.doctorsController.rejectApplicant + ) + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index a615530..ec25e2f 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -489,6 +489,143 @@ export class DoctorService { })); } + public async approveApplicant(announcementId: string, nurseId: string): Promise { + const application = await prisma.announcementNurse.findUnique({ + where: { + announcement_id_nurse_id: { + announcement_id: announcementId, + nurse_id: nurseId, + } + }, + select: { + status: true, + announcement: { + select: { + doctor_id: true, + clinic_id: true, + status: true, + working_days: { + select: { + day_of_week: true, + start_time: true, + end_time: true, + } + } + } + } + } + }); + + if (!application) { + const error = createBilingualError(404, ErrorMessages.APPLICATION_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (application.status !== 'PENDING') { + const error = createBilingualError(409, ErrorMessages.APPLICATION_ALREADY_PROCESSED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (application.announcement.status === 'EXPIRED') { + const error = createBilingualError(400, ErrorMessages.ANNOUNCEMENT_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.$transaction(async (tx) => { + await tx.announcementNurse.update({ + where: { + announcement_id_nurse_id: { + announcement_id: announcementId, + nurse_id: nurseId, + } + }, + data: { + status: 'APPROVED' + } + }); + + // await tx.announcementNurse.updateMany({ + // where: { + // announcement_id: announcementId, + // nurse_id: { not: nurseId }, + // status: 'PENDING' + // }, + // data: { + // status: 'DISABLED' + // } + // }); + + await tx.announcement.update({ + where: { + id: announcementId + }, + data: { + status: 'EXPIRED', + deleted_at: new Date() + } + }); + await tx.nurseSchedule.createMany({ + data: application.announcement.working_days.map(workDay => ({ + nurse_id: nurseId, + doctor_id: application.announcement.doctor_id, + clinic_id: application.announcement.clinic_id, + day_of_week: workDay.day_of_week, + start_time: workDay.start_time, + end_time: workDay.end_time, + is_online: !application.announcement.clinic_id, + is_active: true, + })) + }); + }); + } + + public async rejectApplicant(announcementId: string, nurseId: string): Promise { + const application = await prisma.announcementNurse.findUnique({ + where: { + announcement_id_nurse_id: { + announcement_id: announcementId, + nurse_id: nurseId, + } + }, + select: { + status: true, + announcement: { + select: { + doctor_id: true, + status: true, + } + } + } + }); + + if (!application) { + const error = createBilingualError(404, ErrorMessages.APPLICATION_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (application.status !== 'PENDING') { + const error = createBilingualError(409, ErrorMessages.APPLICATION_ALREADY_PROCESSED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (application.announcement.status === 'EXPIRED') { + const error = createBilingualError(400, ErrorMessages.ANNOUNCEMENT_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.announcementNurse.update({ + where: { + announcement_id_nurse_id: { + announcement_id: announcementId, + nurse_id: nurseId, + } + }, + data: { + status: 'REJECTED' + } + }); + } + public async getAnnouncementApplicants(doctorId: string, announcementId: string): Promise { const announcement = await prisma.announcement.findUnique({ where: { diff --git a/src/swagger-output.json b/src/swagger-output.json index c21254c..d7238b5 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3717,6 +3717,128 @@ } } }, + "/doctors/announcements/{applicantId}/approve": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "Approves a nurse applicant for a specific announcement", + "parameters": [ + { + "name": "applicantId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the nurse applicant to approve" + }, + { + "name": "announcementId", + "in": "query", + "description": "ID of the announcement the applicant applied to", + "required": true, + "type": "string" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Applicant approved successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Applicant approved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم قبول المتقدم بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – announcement does not belong to this doctor" + }, + "404": { + "description": "Applicant or announcement not found" + } + } + } + }, + "/doctors/announcements/{applicantId}/reject": { + "patch": { + "tags": [ + "Doctors" + ], + "description": "Rejects a nurse applicant for a specific announcement", + "parameters": [ + { + "name": "applicantId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the nurse applicant to reject" + }, + { + "name": "announcementId", + "in": "query", + "description": "ID of the announcement the applicant applied to", + "required": true, + "type": "string" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Applicant rejected successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Applicant rejected successfully" + }, + "messageAr": { + "type": "string", + "example": "تم رفض المتقدم بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – announcement does not belong to this doctor" + }, + "404": { + "description": "Applicant or announcement not found" + } + } + } + }, "/clinics": { "post": { "tags": [ @@ -6888,6 +7010,60 @@ } } } + }, + "/nurses/announcements/{announcementId}/apply": { + "post": { + "tags": [ + "Nurses" + ], + "description": "Apply to a specific announcement", + "parameters": [ + { + "name": "announcementId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the announcement to apply for" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Application submitted successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Applied to announcement successfully" + }, + "messageAr": { + "type": "string", + "example": "تم التقديم على الإعلان بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Invalid announcement ID / already applied / validation error" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Announcement not found / Nurse account not approved (PENDING or REJECTED)" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 5a80a3c..3525a21 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -114,6 +114,10 @@ export const ErrorMessages = { en: 'You are not authorized to access this', ar: 'ليس لديك صلاحية للوصول إلى هذا ', }, + DOCTOR_ID_NOT_FOUND: { + en: 'Doctor ID not found in request', + ar: 'معرف الطبيب غير موجود في الطلب', + }, // nurse NURSE_PASSWORD_ALREADY_SET: { @@ -140,6 +144,14 @@ export const ErrorMessages = { en: 'You have already applied to this announcement', ar: 'لقد تقدمت بالفعل لهذا الإعلان', }, + APPLICATION_NOT_FOUND: { + en: 'Application not found', + ar: 'الطلب غير موجود', + }, + APPLICATION_ALREADY_PROCESSED: { + en: 'This application has already been processed', + ar: 'تمت معالجة هذا الطلب بالفعل', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 5e5c3bd..f4effa2 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -148,6 +148,14 @@ export const SuccessResponseMessages = { message_en: "Applied to announcement successfully.", message_ar: "تم التقديم للإعلان بنجاح.", }, + APPLICANT_APPROVED_SUCCESSFULLY: { + message_en: "Applicant approved successfully.", + message_ar: "تم الموافقة على المتقدم بنجاح.", + }, + APPLICANT_REJECTED_SUCCESSFULLY: { + message_en: "Applicant rejected successfully.", + message_ar: "تم رفض المتقدم بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From dccb4726f159801b0d39a40db2dd2badb755abc7 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 24 Feb 2026 14:20:21 +0200 Subject: [PATCH 160/210] delete announcement by doctor --- src/controllers/doctor.controller.ts | 15 ++++++++ src/routes/doctors.route.ts | 40 +++++++++++++++++++++ src/services/doctor.service.ts | 43 +++++++++++++++++++++- src/swagger-output.json | 54 ++++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 +++ 5 files changed, 155 insertions(+), 1 deletion(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 5256392..53160bb 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -142,4 +142,19 @@ export class DoctorController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLICANT_REJECTED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } + + public deleteAnnouncement = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const { announcementId } = req.params; + + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.doctorService.deleteAnnouncement(doctorId, announcementId); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENT_DELETED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index a5f95cb..84574c9 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -448,5 +448,45 @@ export class DoctorsRoute implements Routes { this.doctorsController.rejectApplicant ) + this.router.delete( + `/doctors/announcements/:announcementId`, + /* + #swagger.path = '/doctors/announcements/{announcementId}' + #swagger.method = 'delete' + #swagger.tags = ['Doctors'] + #swagger.description = 'Deletes a specific nurse hiring announcement' + #swagger.parameters['announcementId'] = { + in: 'path', + description: 'ID of the announcement to delete', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Announcement deleted successfully', + schema: { + messageEn: 'Announcement deleted successfully', + messageAr: 'تم حذف الإعلان بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – announcement does not belong to this doctor' + } + #swagger.responses[404] = { + description: 'Announcement not found' + } + */ + AuthMiddleware, + this.doctorsController.deleteAnnouncement + ) + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index ec25e2f..0a8c650 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -540,7 +540,9 @@ export class DoctorService { } }, data: { - status: 'APPROVED' + status: 'APPROVED', + doctor_id: application.announcement.doctor_id, + clinic_id: application.announcement.clinic_id, } }); @@ -626,6 +628,45 @@ export class DoctorService { }); } + public async deleteAnnouncement(doctorId: string, announcementId: string): Promise { + const announcement = await prisma.announcement.findUnique({ + where: { + id: announcementId + }, + select: { + doctor_id: true, + status: true + } + }); + + if (!announcement) { + const error = createBilingualError(404, ErrorMessages.ANNOUNCEMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (announcement.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (announcement.status === 'EXPIRED') { + const error = createBilingualError(400, ErrorMessages.ANNOUNCEMENT_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await prisma.announcement.update({ + where: { + id: announcementId + }, + data: { + status: 'EXPIRED', + deleted_at: new Date() + } + }); + } + + + public async getAnnouncementApplicants(doctorId: string, announcementId: string): Promise { const announcement = await prisma.announcement.findUnique({ where: { diff --git a/src/swagger-output.json b/src/swagger-output.json index d7238b5..26ee789 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3839,6 +3839,60 @@ } } }, + "/doctors/announcements/{announcementId}": { + "delete": { + "tags": [ + "Doctors" + ], + "description": "Deletes a specific nurse hiring announcement", + "parameters": [ + { + "name": "announcementId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the announcement to delete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Announcement deleted successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Announcement deleted successfully" + }, + "messageAr": { + "type": "string", + "example": "تم حذف الإعلان بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – announcement does not belong to this doctor" + }, + "404": { + "description": "Announcement not found" + } + } + } + }, "/clinics": { "post": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index f4effa2..47d67c4 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -156,6 +156,10 @@ export const SuccessResponseMessages = { message_en: "Applicant rejected successfully.", message_ar: "تم رفض المتقدم بنجاح.", }, + ANNOUNCEMENT_DELETED_SUCCESSFULLY: { + message_en: "Announcement deleted successfully.", + message_ar: "تم حذف الإعلان بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From 4cbc2a3ed92f21af0d2cb2cdc68c0d9a5f72968a Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 24 Feb 2026 15:24:07 +0200 Subject: [PATCH 161/210] edit an announcement by doctors --- src/controllers/doctor.controller.ts | 18 ++++- src/dtos/doctors.dto.ts | 30 +++++++ src/routes/doctors.route.ts | 67 +++++++++++++++- src/services/doctor.service.ts | 46 ++++++++++- src/swagger-output.json | 112 +++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 + 6 files changed, 274 insertions(+), 3 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index 53160bb..e97117e 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -1,5 +1,5 @@ -import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto, EditAnnouncementDto } from "@/dtos/doctors.dto"; import { RequestWithUser } from "@/interfaces"; import { DoctorService } from "@/services/doctor.service"; import { UserService } from "@/services/user.service"; @@ -157,4 +157,20 @@ export class DoctorController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENT_DELETED_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); } + + public editAnnouncement = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + const { announcementId } = req.params; + const announcementData: EditAnnouncementDto = req.body; + + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.doctorService.editAnnouncement(doctorId, announcementId, announcementData); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.ANNOUNCEMENT_EDITED_SUCCESSFULLY); + res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + } } \ No newline at end of file diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index a93ac25..442f31d 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -104,6 +104,36 @@ export class PostAnnouncementDto { @Min(0) public years_of_experience?: number; + @IsOptional() + @IsString() + public notes?: string; +} + +export class EditAnnouncementDto { + @IsOptional() + @IsString() + public clinic_id: string; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => WorkingDayDto) + public working_days: WorkingDayDto[]; + + @IsOptional() + @IsEnum(Gender) + public gender?: Gender; + + @IsOptional() + @IsInt() + @Min(0) + public max_age?: number; + + @IsOptional() + @IsInt() + @Min(0) + public years_of_experience?: number; + @IsOptional() @IsString() public notes?: string; diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 84574c9..8c092ba 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -1,5 +1,5 @@ import { DoctorController } from "@/controllers/doctor.controller"; -import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSetPasswordRequestDto, DoctorSignupRequestDto, PostAnnouncementDto, EditAnnouncementDto } from "@/dtos/doctors.dto"; import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; @@ -488,5 +488,70 @@ export class DoctorsRoute implements Routes { this.doctorsController.deleteAnnouncement ) + this.router.patch( + `/doctors/announcements/:announcementId`, + /* + #swagger.path = '/doctors/announcements/{announcementId}' + #swagger.method = 'patch' + #swagger.tags = ['Doctors'] + #swagger.description = 'Edits a specific nurse hiring announcement (only if it is still PENDING)' + #swagger.parameters['announcementId'] = { + in: 'path', + description: 'ID of the announcement to edit', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Updated announcement data (only include fields to be updated)', + required: true, + schema: { + $clinic_id: 'uuid-of-the-clinic', + $working_days: [ + { + $day_of_week: 'MONDAY', + $start_time: '09:00', + $end_time: '17:00' + }, + { + $day_of_week: 'TUESDAY', + $start_time: '10:00', + $end_time: '17:00' + } + ], + gender: 'FEMALE', + max_age: 40, + years_of_experience: 3, + notes: 'Looking for an experienced nurse' + } + } + #swagger.responses[200] = { + description: 'Announcement updated successfully', + schema: { + messageEn: 'Announcement updated successfully', + messageAr: 'تم تحديث الإعلان بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden – announcement does not belong to this doctor or is not PENDING' + } + #swagger.responses[404] = { + description: 'Announcement not found' + } + */ + AuthMiddleware, + ValidationMiddleware(EditAnnouncementDto), + this.doctorsController.editAnnouncement + ) + } } \ No newline at end of file diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 0a8c650..46301ae 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -1,4 +1,4 @@ -import { DoctorLoginRequestDto, DoctorSignupRequestDto, PostAnnouncementDto } from "@/dtos/doctors.dto"; +import { DoctorLoginRequestDto, DoctorSignupRequestDto, PostAnnouncementDto, EditAnnouncementDto } from "@/dtos/doctors.dto"; import { Service } from "typedi"; import { HttpException } from "@/exceptions/HttpException"; import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; @@ -665,7 +665,51 @@ export class DoctorService { }); } + public async editAnnouncement(doctorId: string, announcementId: string, data: EditAnnouncementDto): Promise { + const updateData: any = { ...data }; + const announcement = await prisma.announcement.findUnique({ + where: { + id: announcementId + }, + select: { + doctor_id: true, + status: true + } + }); + + if (!announcement) { + const error = createBilingualError(404, ErrorMessages.ANNOUNCEMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (announcement.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (announcement.status === 'EXPIRED') { + const error = createBilingualError(400, ErrorMessages.ANNOUNCEMENT_EXPIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + if (data.working_days !== undefined) { + updateData.working_days = { + deleteMany: {}, + create: data.working_days.map(day => ({ + day_of_week: day.day_of_week, + start_time: day.start_time, + end_time: day.end_time + })) + }; + } + + await prisma.announcement.update({ + where: { + id: announcementId + }, + data: updateData + }); + } public async getAnnouncementApplicants(doctorId: string, announcementId: string): Promise { const announcement = await prisma.announcement.findUnique({ diff --git a/src/swagger-output.json b/src/swagger-output.json index 26ee789..a754f52 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3891,6 +3891,118 @@ "description": "Announcement not found" } } + }, + "patch": { + "tags": [ + "Doctors" + ], + "description": "Edits a specific nurse hiring announcement (only if it is still PENDING)", + "parameters": [ + { + "name": "announcementId", + "in": "path", + "required": true, + "type": "string", + "description": "ID of the announcement to edit" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Updated announcement data (only include fields to be updated)", + "required": true, + "schema": { + "type": "object", + "properties": { + "clinic_id": { + "type": "string", + "example": "uuid-of-the-clinic" + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "TUESDAY" + }, + "start_time": { + "type": "string", + "example": "10:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + }, + "required": [ + "day_of_week", + "start_time", + "end_time" + ] + } + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "max_age": { + "type": "number", + "example": 40 + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "notes": { + "type": "string", + "example": "Looking for an experienced nurse" + } + }, + "required": [ + "clinic_id", + "working_days" + ] + } + } + ], + "responses": { + "200": { + "description": "Announcement updated successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Announcement updated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم تحديث الإعلان بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "403": { + "description": "Forbidden – announcement does not belong to this doctor or is not PENDING" + }, + "404": { + "description": "Announcement not found" + } + } } }, "/clinics": { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 47d67c4..d68f3ea 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -160,6 +160,10 @@ export const SuccessResponseMessages = { message_en: "Announcement deleted successfully.", message_ar: "تم حذف الإعلان بنجاح.", }, + ANNOUNCEMENT_EDITED_SUCCESSFULLY: { + message_en: "Announcement edited successfully.", + message_ar: "تم تعديل الإعلان بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From 4c44ecfe7afb6a5221408f5264dac0b152441964 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 24 Feb 2026 22:07:45 +0200 Subject: [PATCH 162/210] get all announcements the nurse has applied to --- src/controllers/nurse.controller.ts | 11 +++ src/interfaces/nurse.interface.ts | 26 ++++- src/routes/nurse.route.ts | 66 +++++++++++++ src/services/nurse.service.ts | 98 ++++++++++++++++++- src/swagger-output.json | 145 ++++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 + 6 files changed, 347 insertions(+), 3 deletions(-) diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts index 254c2e3..ae57bad 100644 --- a/src/controllers/nurse.controller.ts +++ b/src/controllers/nurse.controller.ts @@ -57,6 +57,17 @@ export class NurseController { res.status(200).json({ data: announcements, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); + public getNurseApplications = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { + const nurseId = req.user?.id; + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const applications = await this.nurseService.getNurseApplications(nurseId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLICATIONS_RETRIEVED); + res.status(200).json({ data: applications, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); + public applyToAnnouncement = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { const nurseId = req.user?.id; const announcementId = req.params.announcementId; diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts index a6036ce..166c86f 100644 --- a/src/interfaces/nurse.interface.ts +++ b/src/interfaces/nurse.interface.ts @@ -1,4 +1,5 @@ -import { NurseAccountStatus, Gender, AnnouncementStatus} from "@prisma/client"; +import { NurseAccountStatus, Gender, AnnouncementStatus, AnnouncementNurseStatus} from "@prisma/client"; +import { WorkingDays } from "./doctors.interface"; export interface NurseLoginData { id: string, @@ -24,4 +25,27 @@ export interface NurseData { nationalCardUrl: string; brief: string | null; bonusFileUrl: string | null; +} + +export interface NurseApplications { + id: string; + application_status: AnnouncementNurseStatus; + doctor: { + id: string; + name: string; + gender: Gender; + profilePic: string; + }; + clinic: { + id: string; + name: string + address: string; + address_maps_link: string; + }; + working_days: WorkingDays[]; + status?: AnnouncementStatus; + gender?: Gender; + max_age?: number; + years_of_experience?: number; + notes?: string; } \ No newline at end of file diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index 337619b..187953c 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -263,6 +263,72 @@ export class NurseRoute implements Routes { this.nursesController.getAllAnnouncements ); + this.router.get( + `${this.path}/applications`, + /* + #swagger.path = '/nurses/applications' + #swagger.method = 'get' + #swagger.tags = ['Nurses'] + #swagger.description = 'get all announcements the nurse has applied to' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Applications retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + application_status: 'PENDING', + doctor: { + id: 'uuid-string', + name: 'Dr. House', + gender: 'MALE', + profilePic: 'https://res.cloudinary.com/example/image.jpg' + }, + clinic: { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...' + }, + working_days: [ + { + day_of_week: 'MONDAY', + start_time: '09:00', + end_time: '17:00' + } + ], + status: 'POSTED', + gender: 'FEMALE', + max_age: 40, + years_of_experience: 3, + notes: 'Looking for an experienced nurse' + } + ], + messageEn: 'Applications retrieved successfully', + messageAr: 'تم استرجاع الطلبات بنجاح' + } + } + #swagger.responses[400] = { + description: 'Nurse ID not found in token' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Nurse account not approved (PENDING or REJECTED)' + } + */ + AuthMiddleware, + this.nursesController.getNurseApplications + ); + this.router.post( `${this.path}/announcements/:announcementId/apply`, /* diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index 00e01fb..005ea5d 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -4,7 +4,7 @@ import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { hash, compare } from "bcrypt"; import { AuthService } from "./auth.service"; import { NurseSignupRequestDto, NurseLoginRequestDto } from "@/dtos/nurses.dto"; -import { NurseLoginData } from "@/interfaces/nurse.interface"; +import { NurseLoginData, NurseApplications } from "@/interfaces/nurse.interface"; import { NURSE_FILES } from "@/interfaces"; import prisma from '@/config/prisma'; import { Role, NurseAccountStatus } from "@prisma/client"; @@ -218,7 +218,101 @@ export class NurseService { }); } - public async getAllAnnouncements(nurseId): Promise { + public async getNurseApplications(nurseId: string): Promise { + const nurseData = await prisma.nurse.findUnique({ + where: { + id: nurseId + }, + select: { + account_status: true + } + }); + + if (!nurseData || nurseData.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(404, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const applications = await prisma.announcementNurse.findMany({ + where: { + nurse_id: nurseId + }, + select: { + id: true, + status: true, + announcement: { + select: { + id: true, + doctor: { + select: { + user: { + select: { + id: true, + name: true, + gender: true, + photo_url: true, + } + } + } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + }, + working_days: { + select: { + day_of_week: true, + start_time: true, + end_time: true, + } + }, + status: true, + gender: true, + max_age: true, + years_of_experience: true, + notes: true, + } + }, + } + }); + + if (!applications.length) { + return []; + } + + return applications.map(application => ({ + id: application.announcement.id, + application_status: application.status, + doctor: { + id: application.announcement.doctor.user.id, + name: application.announcement.doctor.user.name, + gender: application.announcement.doctor.user.gender, + profilePic: application.announcement.doctor.user.photo_url, + }, + clinic: { + id: application.announcement.clinic.id, + name: application.announcement.clinic.name, + address: application.announcement.clinic.address, + address_maps_link: application.announcement.clinic.address_maps_link, + }, + working_days: application.announcement.working_days.map(workDay => ({ + day_of_week: workDay.day_of_week, + start_time: workDay.start_time, + end_time: workDay.end_time, + })), + status: application.announcement.status, + gender: application.announcement.gender || undefined, + max_age: application.announcement.max_age || undefined, + years_of_experience: application.announcement.years_of_experience || undefined, + notes: application.announcement.notes || undefined, + })); + } + + public async getAllAnnouncements(nurseId: string): Promise { const nurseData = await prisma.nurse.findUnique({ where: { id: nurseId }, select: { account_status: true } diff --git a/src/swagger-output.json b/src/swagger-output.json index a754f52..3621b77 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -7177,6 +7177,151 @@ } } }, + "/nurses/applications": { + "get": { + "tags": [ + "Nurses" + ], + "description": "get all announcements the nurse has applied to", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Applications retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "application_status": { + "type": "string", + "example": "PENDING" + }, + "doctor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Dr. House" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/image.jpg" + } + } + }, + "clinic": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Al Salam Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + } + } + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "MONDAY" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + } + } + }, + "status": { + "type": "string", + "example": "POSTED" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "max_age": { + "type": "number", + "example": 40 + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "notes": { + "type": "string", + "example": "Looking for an experienced nurse" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Applications retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الطلبات بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Nurse ID not found in token" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Nurse account not approved (PENDING or REJECTED)" + } + } + } + }, "/nurses/announcements/{announcementId}/apply": { "post": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index d68f3ea..cf3f17c 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -164,6 +164,10 @@ export const SuccessResponseMessages = { message_en: "Announcement edited successfully.", message_ar: "تم تعديل الإعلان بنجاح.", }, + APPLICATIONS_RETRIEVED: { + message_en: "Applications retrieved successfully.", + message_ar: "تم استرجاع الطلبات بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From 3233d473343235c7bd7f228d522d4f84199d9867 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 25 Feb 2026 19:42:53 +0200 Subject: [PATCH 163/210] get schedule for a specific nurse --- src/controllers/nurse.controller.ts | 11 ++ src/interfaces/nurse.interface.ts | 17 +++ .../migration.sql | 22 ++++ src/prisma/schema.prisma | 4 +- src/routes/nurse.route.ts | 60 +++++++++ src/services/nurse.service.ts | 112 ++++++++++++++-- src/swagger-output.json | 121 ++++++++++++++++++ src/utils/responseMessages.ts | 4 + 8 files changed, 340 insertions(+), 11 deletions(-) create mode 100644 src/prisma/migrations/20260224215850_add_doctor_relation_for_nurse_schedule/migration.sql diff --git a/src/controllers/nurse.controller.ts b/src/controllers/nurse.controller.ts index ae57bad..a6c3d18 100644 --- a/src/controllers/nurse.controller.ts +++ b/src/controllers/nurse.controller.ts @@ -79,4 +79,15 @@ export class NurseController { const responseMessage = createMultiLangMessage(SuccessResponseMessages.APPLIED_TO_ANNOUNCEMENT_SUCCESSFULLY); res.status(200).json({ messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); }); + + public getNurseSchedule = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction) => { + const nurseId = req.user?.id; + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const schedule = await this.nurseService.getNurseSchedule(nurseId); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_SCHEDULE_RETRIEVED); + res.status(200).json({ data: schedule, messageEn: responseMessage.messageEn, messageAr: responseMessage.messageAr }); + }); } \ No newline at end of file diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts index 166c86f..e0bd040 100644 --- a/src/interfaces/nurse.interface.ts +++ b/src/interfaces/nurse.interface.ts @@ -48,4 +48,21 @@ export interface NurseApplications { max_age?: number; years_of_experience?: number; notes?: string; +} + +export interface NurseSchedule { + id: string; + doctor: { + id: string; + name: string; + gender: Gender; + profilePic: string; + }; + clinic: { + id: string; + name: string + address: string; + address_maps_link: string; + }; + working_days: WorkingDays[]; } \ No newline at end of file diff --git a/src/prisma/migrations/20260224215850_add_doctor_relation_for_nurse_schedule/migration.sql b/src/prisma/migrations/20260224215850_add_doctor_relation_for_nurse_schedule/migration.sql new file mode 100644 index 0000000..aa37129 --- /dev/null +++ b/src/prisma/migrations/20260224215850_add_doctor_relation_for_nurse_schedule/migration.sql @@ -0,0 +1,22 @@ +/* + Warnings: + + - The values [POSTED] on the enum `AnnouncementStatus` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "AnnouncementStatus_new" AS ENUM ('PENDING', 'EXPIRED'); +ALTER TABLE "public"."Announcements" ALTER COLUMN "status" DROP DEFAULT; +ALTER TABLE "Announcements" ALTER COLUMN "status" TYPE "AnnouncementStatus_new" USING ("status"::text::"AnnouncementStatus_new"); +ALTER TYPE "AnnouncementStatus" RENAME TO "AnnouncementStatus_old"; +ALTER TYPE "AnnouncementStatus_new" RENAME TO "AnnouncementStatus"; +DROP TYPE "public"."AnnouncementStatus_old"; +ALTER TABLE "Announcements" ALTER COLUMN "status" SET DEFAULT 'PENDING'; +COMMIT; + +-- AlterTable +ALTER TABLE "AnnouncementNurses" ADD COLUMN "deleted_at" TIMESTAMP(3); + +-- AddForeignKey +ALTER TABLE "NurseSchedules" ADD CONSTRAINT "NurseSchedules_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "Doctor"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 885167b..1732ad5 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -75,6 +75,7 @@ model Doctor { vacations Vacation[] announcements Announcement[] @relation("AnnouncementDoctor") announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseDoctor") + nurseSchedules NurseSchedule[] @relation("NurseScheduleDoctor") @@map("Doctor") } @@ -317,6 +318,7 @@ model NurseSchedule { modified_at DateTime @updatedAt deleted_at DateTime? nurse Nurse @relation("NurseScheduleNurse", fields: [nurse_id], references: [id], onDelete: Cascade) + doctor Doctor @relation("NurseScheduleDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) userId String? @@ -395,6 +397,7 @@ model AnnouncementNurse { clinic_id String? created_at DateTime @default(now()) modified_at DateTime @updatedAt + deleted_at DateTime? announcement Announcement @relation("AnnouncementNurses", fields: [announcement_id], references: [id], onDelete: Cascade) @@ -496,7 +499,6 @@ enum NurseAccountStatus { } enum AnnouncementStatus { - POSTED PENDING EXPIRED } diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index 187953c..6677cce 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -370,5 +370,65 @@ export class NurseRoute implements Routes { AuthMiddleware, this.nursesController.applyToAnnouncement ) + + this.router.get( + `${this.path}/schedule`, + /* + #swagger.path = '/nurses/schedule' + #swagger.method = 'get' + #swagger.tags = ['Nurses'] + #swagger.description = 'get the schedule for the nurse' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Schedule retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + doctor: { + id: 'uuid-string', + name: 'Dr. House', + gender: 'MALE', + profilePic: 'https://res.cloudinary.com/example/image.jpg' + }, + clinic: { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...' + }, + working_days: [ + { + day_of_week: 'MONDAY', + start_time: '09:00', + end_time: '17:00' + } + ] + } + ], + messageEn: 'Nurse schedule retrieved successfully', + messageAr: 'تم استرجاع جدول الممرضة بنجاح' + } + } + #swagger.responses[400] = { + description: 'Nurse ID not found in token' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Nurse account not approved or no schedule assigned' + } + */ + AuthMiddleware, + this.nursesController.getNurseSchedule + ); } } \ No newline at end of file diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index 005ea5d..7683680 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -4,7 +4,7 @@ import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { hash, compare } from "bcrypt"; import { AuthService } from "./auth.service"; import { NurseSignupRequestDto, NurseLoginRequestDto } from "@/dtos/nurses.dto"; -import { NurseLoginData, NurseApplications } from "@/interfaces/nurse.interface"; +import { NurseLoginData, NurseApplications, NurseSchedule } from "@/interfaces/nurse.interface"; import { NURSE_FILES } from "@/interfaces"; import prisma from '@/config/prisma'; import { Role, NurseAccountStatus } from "@prisma/client"; @@ -218,13 +218,107 @@ export class NurseService { }); } + public async getNurseSchedule(nurseId: string): Promise { + const nurseData = await prisma.nurse.findUnique({ + where: { + id: nurseId + }, + select: { + account_status: true + } + }); + + if (nurseData.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(404, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const schedules = await prisma.nurseSchedule.findMany({ + where: { + nurse_id: nurseId, + is_active: true, + deleted_at: null, + }, + select: { + id: true, + doctor: { + select: { + user: { + select: { + id: true, + name: true, + gender: true, + photo_url: true, + } + } + } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + }, + day_of_week: true, + start_time: true, + end_time: true, + } + }); + + if (!schedules.length) { + return []; + } + + const groupedMap = new Map(); + + for (const schedule of schedules) { + const key = `${schedule.doctor.user.id}_${schedule.clinic?.id}`; + + if (groupedMap.has(key)) { + groupedMap.get(key).working_days.push({ + day_of_week: schedule.day_of_week, + start_time: schedule.start_time, + end_time: schedule.end_time, + }); + } + else { + groupedMap.set(key, { + id: schedule.id, + doctor: { + id: schedule.doctor.user.id, + name: schedule.doctor.user.name, + gender: schedule.doctor.user.gender, + profilePic: schedule.doctor.user.photo_url, + }, + clinic: { + id: schedule.clinic?.id || null, + name: schedule.clinic?.name || null, + address: schedule.clinic?.address || null, + address_maps_link: schedule.clinic?.address_maps_link || null, + }, + working_days: [ + { + day_of_week: schedule.day_of_week, + start_time: schedule.start_time, + end_time: schedule.end_time, + } + ], + }); + } + } + + return Array.from(groupedMap.values()); + } + public async getNurseApplications(nurseId: string): Promise { const nurseData = await prisma.nurse.findUnique({ - where: { - id: nurseId + where: { + id: nurseId }, - select: { - account_status: true + select: { + account_status: true } }); @@ -234,8 +328,8 @@ export class NurseService { } const applications = await prisma.announcementNurse.findMany({ - where: { - nurse_id: nurseId + where: { + nurse_id: nurseId }, select: { id: true, @@ -325,9 +419,7 @@ export class NurseService { const announcements = await prisma.announcement.findMany({ where: { deleted_at: null, - status: { - in: ['POSTED', 'PENDING'] - } + status: 'PENDING', }, select: { id: true, diff --git a/src/swagger-output.json b/src/swagger-output.json index 3621b77..dd1b352 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -7375,6 +7375,127 @@ } } } + }, + "/nurses/schedule": { + "get": { + "tags": [ + "Nurses" + ], + "description": "get the schedule for the nurse", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Schedule retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "doctor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Dr. House" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/image.jpg" + } + } + }, + "clinic": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Al Salam Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + } + } + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "MONDAY" + }, + "start_time": { + "type": "string", + "example": "09:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + } + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse schedule retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع جدول الممرضة بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Nurse ID not found in token" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Nurse account not approved or no schedule assigned" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index cf3f17c..2064546 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -168,6 +168,10 @@ export const SuccessResponseMessages = { message_en: "Applications retrieved successfully.", message_ar: "تم استرجاع الطلبات بنجاح.", }, + NURSE_SCHEDULE_RETRIEVED: { + message_en: "Nurse schedule retrieved successfully.", + message_ar: "تم استرجاع جدول الممرضة بنجاح.", + }, // Success messages for Google Auth PHONE_NUMBER_UPDATED_SUCCESSFULLY: { From a5a5361c1370fdfdfe9c6ee5ac02b90e2de210cd Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 25 Feb 2026 21:34:04 +0200 Subject: [PATCH 164/210] get all appointments for a specific doctor by nurse --- src/controllers/appointment.controller.ts | 35 +++++- src/interfaces/appointments.interface.ts | 31 ++++- src/routes/nurse.route.ts | 84 +++++++++++++ src/services/appointment.service.ts | 80 +++++++++++- src/services/nurse.service.ts | 1 + src/swagger-output.json | 142 ++++++++++++++++++++++ src/utils/responseMessages.ts | 4 + 7 files changed, 367 insertions(+), 10 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 7208a8e..3ca0929 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -446,5 +446,38 @@ export class AppointmentController { res.status(200).json({ ...response }); - }) + }); + + public getAppointmentsByDate = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const nurseId = req.user?.id; + const { doctorId, clinicId, date } = req.query; + + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!date) { + const error = createBilingualError(400, ErrorMessages.DATE_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(date as string)) { + const error = createBilingualError(400, ErrorMessages.INVALID_DATE_FORMAT); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appointments = await this.appointmentService.getAppointmentsByDate(doctorId as string, clinicId as string, date as string); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENTS_BY_NURSE_RETRIEVED); + res.status(200).json({ + data: appointments, + ...response + }); + }); } \ No newline at end of file diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index da60627..ff2e7ab 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -1,5 +1,5 @@ import { User } from './users.interface'; -import { AppointmentStatus, DayOfWeek, VacationStatus } from '@prisma/client' +import { AppointmentStatus, DayOfWeek, VacationStatus, Gender } from '@prisma/client' export interface Appointment { id: string; @@ -110,16 +110,16 @@ export interface checkExistingAppointments { } export interface ConflictingAppointment { - id: string; - scheduled_time: Date; + id: string; + scheduled_time: Date; } export interface Vacations { vacationId: string; scheduleId: string; clinicId: string | null; - clinicName: string | null; - clinicAddress: string | null; + clinicName: string | null; + clinicAddress: string | null; dayOfWeek: DayOfWeek; isOnline: boolean; status: VacationStatus; @@ -130,4 +130,25 @@ export interface DoctorVacations { breakStart: string; breakEnd: string; vacations: Vacations[]; +} + +export interface AppointmentData { + id: string; + clinic: { + id: string; + name: string; + address: string; + address_maps_link: string; + }; + patient: { + id: string; + name: string + gender: Gender; + phone: string; + }; + status: AppointmentStatus; + slot_duration: number; + appointment_date: string; + start_time: string; + end_time: string; } \ No newline at end of file diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index 6677cce..b022761 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -2,6 +2,7 @@ import { Routes } from "@/interfaces"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { Router } from "express"; import { NurseController } from "@/controllers/nurse.controller"; +import { AppointmentController } from "@/controllers/appointment.controller"; import { NurseLoginRequestDto, NurseSetPasswordRequestDto, NurseSignupRequestDto } from "@/dtos/nurses.dto"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { Role } from "@prisma/client"; @@ -11,6 +12,7 @@ export class NurseRoute implements Routes { public path = '/nurses' public router = Router(); public nursesController = new NurseController(); + public appointmentController = new AppointmentController(); constructor() { this.initializeRoutes(); } @@ -430,5 +432,87 @@ export class NurseRoute implements Routes { AuthMiddleware, this.nursesController.getNurseSchedule ); + + this.router.get( + `${this.path}/appointments`, + /* + #swagger.path = '/nurses/appointments' + #swagger.method = 'get' + #swagger.tags = ['Nurses'] + #swagger.description = 'Get all appointments for a specific doctor on a given date' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['doctorId'] = { + in: 'query', + description: 'The ID of the doctor whose appointments are being retrieved', + required: true, + type: 'string' + } + + #swagger.parameters['clinicId'] = { + in: 'query', + description: 'The ID of the clinic to filter appointments by', + required: false, + type: 'string' + } + + #swagger.parameters['date'] = { + in: 'query', + description: 'The date to retrieve appointments for, in YYYY-MM-DD format', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Appointments retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + clinic: { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...' + }, + patient: { + id: 'uuid-string', + name: 'Ahmed Hassan', + gender: 'MALE', + phone: '+201012345678' + }, + status: 'CONFIRMED', + slot_duration: 30, + appointment_date: '2025-03-15', + start_time: '09:00 AM', + end_time: '09:30 AM' + } + ], + messageEn: 'Appointments retrieved successfully', + messageAr: 'تم استرجاع المواعيد بنجاح' + } + } + + #swagger.responses[400] = { + description: 'Bad request – missing or invalid parameters (nurseId, doctorId, date)' + } + + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + + #swagger.responses[500] = { + description: 'Internal server error' + } + */ + AuthMiddleware, + this.appointmentController.getAppointmentsByDate + ) } } \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index a84aae4..eed9e84 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations, AppointmentData } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; import { start } from 'repl'; @@ -320,7 +320,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, - address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, + address_maps_link: appointment.clinic ? appointment.clinic.address_maps_link : null, })); } @@ -372,7 +372,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, - address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, + address_maps_link: appointment.clinic ? appointment.clinic.address_maps_link : null, }; } @@ -461,7 +461,7 @@ export class AppointmentService { end_time: this.formatTime(appointment.end_time), clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, - address_maps_link: appointment.clinic? appointment.clinic.address_maps_link : null, + address_maps_link: appointment.clinic ? appointment.clinic.address_maps_link : null, position: appointment.position, estimatedWaitMinutes: appointment.estimated_time, patientsAhead: appointment.patients_ahead @@ -830,6 +830,78 @@ export class AppointmentService { } + public async getAppointmentsByDate(doctorId: string, clinicId: string, date: string): Promise { + const requestedDate = new Date(date); + + const startOfDay = new Date(requestedDate); + startOfDay.setUTCHours(0, 0, 0, 0); + + const endOfDay = new Date(requestedDate); + endOfDay.setUTCHours(23, 59, 59, 999); + + const appointments = await prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + clinic_id: clinicId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay + }, + status: { + in: ['CONFIRMED', 'COMPLETED'] + }, + deleted_at: null, + }, + select: { + id: true, + scheduled_time: true, + end_time: true, + slot_duration: true, + status: true, + patient: { + select: { + id: true, + name: true, + gender: true, + phone: true, + } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + } + }, + orderBy: { + scheduled_time: 'asc' + } + }); + + return appointments.map(appointment => ({ + id: appointment.id, + patient: { + id: appointment.patient.id, + name: appointment.patient.name, + gender: appointment.patient.gender, + phone: appointment.patient.phone, + }, + clinic: { + id: appointment.clinic.id, + name: appointment.clinic.name, + address: appointment.clinic.address, + address_maps_link: appointment.clinic.address_maps_link, + }, + status: appointment.status, + slot_duration: appointment.slot_duration, + appointment_date: this.formatDate(new Date(appointment.scheduled_time)), + start_time: this.formatTime(new Date(appointment.scheduled_time)), + end_time: this.formatTime(new Date(appointment.end_time)), + })); + } + public async cancelDoctorVacation(doctorId: string, vacationId: string, scheduleId: string): Promise { const schedule = await prisma.doctorSchedule.findUnique({ where: { diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index 7683680..f6e1e62 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -5,6 +5,7 @@ import { hash, compare } from "bcrypt"; import { AuthService } from "./auth.service"; import { NurseSignupRequestDto, NurseLoginRequestDto } from "@/dtos/nurses.dto"; import { NurseLoginData, NurseApplications, NurseSchedule } from "@/interfaces/nurse.interface"; +import { AppointmentData } from "@/interfaces"; import { NURSE_FILES } from "@/interfaces"; import prisma from '@/config/prisma'; import { Role, NurseAccountStatus } from "@prisma/client"; diff --git a/src/swagger-output.json b/src/swagger-output.json index dd1b352..90220ca 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -7496,6 +7496,148 @@ } } } + }, + "/nurses/appointments": { + "get": { + "tags": [ + "Nurses" + ], + "description": "Get all appointments for a specific doctor on a given date", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "doctorId", + "in": "query", + "description": "The ID of the doctor whose appointments are being retrieved", + "required": true, + "type": "string" + }, + { + "name": "clinicId", + "in": "query", + "description": "The ID of the clinic to filter appointments by", + "required": false, + "type": "string" + }, + { + "name": "date", + "in": "query", + "description": "The date to retrieve appointments for, in YYYY-MM-DD format", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Appointments retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "clinic": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Al Salam Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + } + } + }, + "patient": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Ahmed Hassan" + }, + "gender": { + "type": "string", + "example": "MALE" + }, + "phone": { + "type": "string", + "example": "+201012345678" + } + } + }, + "status": { + "type": "string", + "example": "CONFIRMED" + }, + "slot_duration": { + "type": "number", + "example": 30 + }, + "appointment_date": { + "type": "string", + "example": "2025-03-15" + }, + "start_time": { + "type": "string", + "example": "09:00 AM" + }, + "end_time": { + "type": "string", + "example": "09:30 AM" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Appointments retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع المواعيد بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request – missing or invalid parameters (nurseId, doctorId, date)" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "500": { + "description": "Internal server error" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 2064546..7e4bb3e 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -291,6 +291,10 @@ export const SuccessResponseMessages = { message_en: 'Online doctors retrieved successfully', message_ar: 'تم استرجاع الأطباء المتاحين عبر الإنترنت بنجاح', }, + APPOINTMENTS_BY_NURSE_RETRIEVED: { + message_en: 'Appointments retrieved to the nurse successfully', + message_ar: 'تم استرجاع المواعيد للممرضة بنجاح', + } } interface MultiLangMessageObj { From beec2ca0485789d028c087cd4c9feb1d3bab51ba Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 25 Feb 2026 22:02:10 +0200 Subject: [PATCH 165/210] mark an appointment as completed by the nurse --- src/controllers/appointment.controller.ts | 16 +++++++ src/routes/nurse.route.ts | 43 +++++++++++++++++ src/services/appointment.service.ts | 25 ++++++++++ src/swagger-output.json | 57 +++++++++++++++++++++++ src/utils/responseMessages.ts | 4 ++ 5 files changed, 145 insertions(+) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 3ca0929..380058e 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -480,4 +480,20 @@ export class AppointmentController { ...response }); }); + + public completeAppointment = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const nurseId = req.user?.id; + const { appointmentId } = req.params; + + if (!nurseId) { + const error = createBilingualError(400, ErrorMessages.NURSE_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.appointmentService.completeAppointment(appointmentId); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_COMPLETED_SUCCESSFULLY); + res.status(200).json({ + ...response + }); + }); } \ No newline at end of file diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index b022761..fceda3c 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -513,6 +513,49 @@ export class NurseRoute implements Routes { */ AuthMiddleware, this.appointmentController.getAppointmentsByDate + ); + + this.router.patch( + `${this.path}/appointments/:appointmentId/complete`, + /* + #swagger.path = '/nurses/appointments/{appointmentId}/complete' + #swagger.method = 'patch' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (must be a nurse)', + required: true, + type: 'string' + } + #swagger.description = 'Mark an appointment as completed. Only accessible by authenticated nurses' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'UUID of the appointment to complete', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Appointment marked as completed successfully', + schema: { + messageEn: 'Appointment completed successfully', + messageAr: 'تم إكمال الموعد بنجاح' + } + } + #swagger.responses[400] = { + description: 'Bad request - missing nurse ID or invalid appointment' + } + #swagger.responses[401] = { + description: 'Unauthorized - missing or invalid token' + } + #swagger.responses[403] = { + description: 'Forbidden - appointment does not belong to the authenticated user' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, + this.appointmentController.completeAppointment ) } } \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index eed9e84..3ee79d8 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -902,6 +902,31 @@ export class AppointmentService { })); } + public async completeAppointment(appointmentId: string): Promise { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + }, + select: { + doctor_id: true, + } + }); + + await this.getAndValidateAppointment(appointmentId, appointment.doctor_id); + + await prisma.appointment.update({ + where: { + id: appointmentId, + deleted_at: null, + }, + data: { + status: 'COMPLETED', + is_completed: true, + deleted_at: new Date(), + } + }) + } + public async cancelDoctorVacation(doctorId: string, vacationId: string, scheduleId: string): Promise { const schedule = await prisma.doctorSchedule.findUnique({ where: { diff --git a/src/swagger-output.json b/src/swagger-output.json index 90220ca..45993d8 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -7638,6 +7638,63 @@ } } } + }, + "/nurses/appointments/{appointmentId}/complete": { + "patch": { + "tags": [ + "Appointments" + ], + "description": "Mark an appointment as completed. Only accessible by authenticated nurses", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the appointment to complete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (must be a nurse)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Appointment marked as completed successfully", + "schema": { + "type": "object", + "properties": { + "messageEn": { + "type": "string", + "example": "Appointment completed successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إكمال الموعد بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing nurse ID or invalid appointment" + }, + "401": { + "description": "Unauthorized - missing or invalid token" + }, + "403": { + "description": "Forbidden - appointment does not belong to the authenticated user" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 7e4bb3e..88708d7 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -294,6 +294,10 @@ export const SuccessResponseMessages = { APPOINTMENTS_BY_NURSE_RETRIEVED: { message_en: 'Appointments retrieved to the nurse successfully', message_ar: 'تم استرجاع المواعيد للممرضة بنجاح', + }, + APPOINTMENT_COMPLETED_SUCCESSFULLY: { + message_en: 'Appointment completed successfully', + message_ar: 'تم إكمال الموعد بنجاح', } } From e45e19088c4c7876096aadbdec45df2291cf71e2 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 10:12:42 +0200 Subject: [PATCH 166/210] get unverified nurses / nurse by id by admin --- src/controllers/admin.controller.ts | 27 ++++ src/dtos/admins.dto.ts | 21 ++- src/routes/admin.route.ts | 103 ++++++++++++ src/services/admin.service.ts | 75 ++++++++- src/swagger-output.json | 234 ++++++++++++++++++++++++++++ src/utils/errorMessages.ts | 4 + src/utils/responseMessages.ts | 10 +- 7 files changed, 470 insertions(+), 4 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 3225e77..2e3b9e3 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -91,6 +91,22 @@ export class AdminController { }); } + public getNurseById = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const nurseId = req.params.id; + const nurse = await this.adminService.getNurseById(nurseId); + if (!nurse) { + const error = createBilingualError(404, ErrorMessages.NURSE_DATA_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_RETRIEVED); + res.status(200).json({ + data: nurse, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } + public getUnverifiedDoctors = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { const unverifiedDoctors = await this.adminService.getUnverifiedDoctors(); @@ -114,6 +130,17 @@ export class AdminController { }); } + public getUnverifiedNurses = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const unverifiedNurses = await this.adminService.getUnverifiedNurses(); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.UNVERIFIED_NURSES_RETRIEVED); + res.status(200).json({ + data: unverifiedNurses, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } + public updateDoctorVerificationStatus = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { const doctorId = req.params.id; diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index bd93f02..0fc301e 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -1,4 +1,4 @@ -import { DoctorAccountStatus, Gender, Role } from "@prisma/client"; +import { DoctorAccountStatus, Gender, Role , NurseAccountStatus} from "@prisma/client"; import { IsEmail, IsNotEmpty, IsString } from "class-validator"; import { IsValidSpecialization } from "@/validators/specialization.validator"; import { TransformSpecialization } from "@/utils/specializationTransform"; @@ -45,4 +45,23 @@ export class DoctorFromAdminResponseDto { unionSpecializationCertificateUrl?: string; professionalPracticeCardUrl?: string; }; +} + +export class NurseFromAdminResponseDto { + public name: string; + public email: string; + public username: string; + public phone: string; + public gender: Gender; + public date_of_birth: Date; + public isVerified: boolean; + public hasCompletedProfile?: boolean + public photoUrl?: string; + public nurse?: { + account_status?: NurseAccountStatus; + years_of_experience?: number; + brief?: string; + nationalCardUrl?: string; + bonusFileUrl?: string; + }; } \ No newline at end of file diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index be21622..1bba813 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -138,6 +138,57 @@ export class AdminRoute implements Routes { this.adminController.getUnverifiedDoctors, ); + this.router.get( + '/admin/nurses/unverified', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.parameters['accept-language'] = { + in: 'header', + description: 'Language preference (en or ar)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Unverified nurses retrieved successfully', + schema: { + data: [ + { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + isVerified: false, + hasCompletedProfile: false, + photo_url: null, + nurse: { + account_status: 'PENDING', + years_of_experience: 3, + brief: 'Experienced nurse in ICU', + nationalCardUrl: '', + bonusFileUrl: '' + } + } + ], + messageEn: 'Unverified nurses retrieved successfully', + messageAr: "تم استرجاع بيانات الممرضين غير المعتمدين بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.getUnverifiedNurses, + ); + + this.router.patch( '/admin/doctors/verify/:id', /* @@ -255,6 +306,58 @@ export class AdminRoute implements Routes { this.adminController.getDoctorById, ); + this.router.get( + '/admin/nurses/:id', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Nurse ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurse retrieved successfully', + schema: { + data: { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + role: 'NURSE', + isVerified: true, + hasCompletedProfile: true, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: 'Experienced nurse in ICU', + nationalCardUrl: '', + bonusFileUrl: '' + } + }, + messageEn: 'Nurse retrieved successfully', + messageAr: "تم استرجاع بيانات الممرض بنجاح." + } + } + #swagger.responses[404] = { + description: 'Nurse not found', + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.getNurseById, + ); + // Clinic routes this.router.get( `${this.path}/clinics`, diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index f307c6d..73b9eee 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -1,7 +1,7 @@ import { DoctorAccountStatus, NurseAccountStatus, PrismaClient, Role } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; -import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto } from '@/dtos/admins.dto'; +import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto, NurseFromAdminResponseDto } from '@/dtos/admins.dto'; import { HttpException } from '@/exceptions/HttpException'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import { User } from '@/interfaces'; @@ -168,6 +168,41 @@ export class AdminService { return doctor; } + public async getNurseById(id: string): Promise { + const nurse = await prisma.user.findUnique({ + where: { id, role: Role.NURSE }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + nurse: { + select: { + account_status: true, + years_of_experience: true, + brief: true, + nationalCardUrl: true, + bonusFileUrl: true, + } + }, + } + }); + + if (!nurse) { + const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return nurse; + } + public async getUnverifiedDoctors(): Promise { const unverifiedDoctors = await prisma.user.findMany({ @@ -200,6 +235,42 @@ export class AdminService { return unverifiedDoctors } + + public async getUnverifiedNurses(): Promise { + + const unverifiedNurses = await prisma.user.findMany({ + where: { + role: Role.NURSE, + nurse: { + account_status: NurseAccountStatus.PENDING + } + }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + nurse: { + select: { + account_status: true, + years_of_experience: true, + brief: true, + nationalCardUrl: true, + bonusFileUrl: true, + } + }, + }, + }); + return unverifiedNurses + + } + public async updateDoctorVerificationStatus(doctorId: string, isApproved: boolean | null): Promise { const doctor = await prisma.user.findUnique({ @@ -233,7 +304,7 @@ export class AdminService { public async sendVerificationStatusEmail(userId: string, isApproved: boolean): Promise { const user = await prisma.user.findUnique({ - where: { id: userId}, + where: { id: userId }, select: { email: true, name: true } }); if (!user) { diff --git a/src/swagger-output.json b/src/swagger-output.json index 45993d8..d5f23bc 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1714,6 +1714,121 @@ } } }, + "/admin/nurses/unverified": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + }, + { + "name": "accept-language", + "in": "header", + "description": "Language preference (en or ar)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Unverified nurses retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "isVerified": { + "type": "boolean", + "example": false + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "PENDING" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": { + "type": "string", + "example": "Experienced nurse in ICU" + }, + "nationalCardUrl": { + "type": "string", + "example": "" + }, + "bonusFileUrl": { + "type": "string", + "example": "" + } + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Unverified nurses retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرضين غير المعتمدين بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/admin/doctors/verify/{id}": { "patch": { "tags": [ @@ -1977,6 +2092,125 @@ } } }, + "/admin/nurses/{id}": { + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Nurse ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurse retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": { + "type": "string", + "example": "Experienced nurse in ICU" + }, + "nationalCardUrl": { + "type": "string", + "example": "" + }, + "bonusFileUrl": { + "type": "string", + "example": "" + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرض بنجاح." + } + }, + "xml": { + "name": "main" + } + } + }, + "404": { + "description": "Nurse not found" + } + } + } + }, "/admin/clinics": { "get": { "tags": [ diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 3525a21..a41b38a 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -152,6 +152,10 @@ export const ErrorMessages = { en: 'This application has already been processed', ar: 'تمت معالجة هذا الطلب بالفعل', }, + NURSE_DATA_NOT_FOUND: { + en: 'Nurse data not found', + ar: 'بيانات الممرضة غير موجودة', + }, // File upload errors NO_FILE_UPLOADED: { diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 88708d7..9989857 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -67,6 +67,13 @@ export const SuccessResponseMessages = { message_ar: "تم تحديث حالة اعتماد الطبيب بنجاح.", }, + // get messages for Nurses by Admin + UNVERIFIED_NURSES_RETRIEVED: { + message_en: "Unverified nurses retrieved successfully.", + message_ar: "تم استرجاع بيانات الممرضات غير المعتمدين بنجاح.", + }, + + // Success messages for Clinics CLINIC_CREATED_SUCCESSFULLY: { message_en: "Clinic created successfully.", @@ -298,7 +305,8 @@ export const SuccessResponseMessages = { APPOINTMENT_COMPLETED_SUCCESSFULLY: { message_en: 'Appointment completed successfully', message_ar: 'تم إكمال الموعد بنجاح', - } + }, + } interface MultiLangMessageObj { From caa22705972887985f6613c921a64bfe723e1198 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 10:37:16 +0200 Subject: [PATCH 167/210] add/get nurses by admin --- src/controllers/admin.controller.ts | 26 ++- src/dtos/admins.dto.ts | 14 +- src/routes/admin.route.ts | 106 +++++++++++- src/services/admin.service.ts | 115 ++++++++++++- src/swagger-output.json | 252 ++++++++++++++++++++++++++++ src/utils/responseMessages.ts | 8 + 6 files changed, 510 insertions(+), 11 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 2e3b9e3..9023040 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express'; import { Container } from 'typedi'; import { AdminService } from '@/services/admin.service'; -import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { AddUserFromAdminDto } from '@/dtos/admins.dto'; import { RequestWithLanguage } from '@/middlewares/language.middleware'; import { formatSpecializationResponse } from '@/utils/specializationTransform'; import { SpecializationKey } from '@/constants/specializations'; @@ -17,7 +17,7 @@ export class AdminController { public clinicService = Container.get(ClinicService); public addDoctor = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { - const doctorData: AddDoctorFromAdminDto = req.body; + const doctorData: AddUserFromAdminDto = req.body; const newDoctor = await this.adminService.addDoctor(doctorData); const formattedNewDoctor = newDoctor.doctor ? { @@ -40,6 +40,17 @@ export class AdminController { }); }; + public addNurse = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + const nurseData: AddUserFromAdminDto = req.body; + const newNurse = await this.adminService.addNurse(nurseData); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSE_CREATED); + res.status(201).json({ + data: newNurse, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + }; + public getAllDoctors = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { const doctors = await this.adminService.getAllDoctors(); @@ -64,6 +75,17 @@ export class AdminController { }); } + public getAllNurses = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { + + const nurses = await this.adminService.getAllNurses(); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSES_RETRIEVED); + res.status(200).json({ + data: nurses, + messageEn: responseMessage.messageEn, + messageAr: responseMessage.messageAr, + }); + } + public getDoctorById = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { diff --git a/src/dtos/admins.dto.ts b/src/dtos/admins.dto.ts index 0fc301e..b3d039b 100644 --- a/src/dtos/admins.dto.ts +++ b/src/dtos/admins.dto.ts @@ -1,9 +1,8 @@ -import { DoctorAccountStatus, Gender, Role , NurseAccountStatus} from "@prisma/client"; -import { IsEmail, IsNotEmpty, IsString } from "class-validator"; -import { IsValidSpecialization } from "@/validators/specialization.validator"; -import { TransformSpecialization } from "@/utils/specializationTransform"; +import { DoctorAccountStatus, Gender, Role, NurseAccountStatus } from "@prisma/client"; +import { IsEmail, IsNotEmpty, IsString, IsInt, IsOptional } from "class-validator"; +import { Type } from "class-transformer"; -export class AddDoctorFromAdminDto { +export class AddUserFromAdminDto { @IsEmail() @IsNotEmpty() public email: string; @@ -21,6 +20,11 @@ export class AddDoctorFromAdminDto { @IsString() public gender: Gender; + + @IsInt() + @Type(() => Number) + @IsOptional() + public years_of_experience: number; } export class DoctorFromAdminResponseDto { diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 1bba813..3911205 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import { AdminController } from '@/controllers/admin.controller'; -import { AddDoctorFromAdminDto } from '@/dtos/admins.dto'; +import { AddUserFromAdminDto } from '@/dtos/admins.dto'; import { Routes } from '@/interfaces'; import { AuthMiddleware, RoleMiddleware } from '@/middlewares/auth.middleware'; import { LanguageMiddleware } from '@/middlewares/language.middleware'; @@ -59,10 +59,67 @@ export class AdminRoute implements Routes { AuthMiddleware, RoleMiddleware(Role.ADMIN), LanguageMiddleware, - ValidationMiddleware(AddDoctorFromAdminDto), + ValidationMiddleware(AddUserFromAdminDto), this.adminController.addDoctor, ); + this.router.post( + '/admin/nurses', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Nurse data', + required: true, + schema: { + $email: 'nurse@example.com', + $name: 'Nurse Jane', + $phone: '1234567890', + $gender: 'MALE or FEMALE', + $date_of_birth: '1995-06-15', + years_of_experience: 3 + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[201] = { + description: 'Nurse added successfully', + schema: { + data: { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + role: 'NURSE', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + isVerified: true, + hasCompletedProfile: false, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: null, + nationalCardUrl: null, + bonusFileUrl: null + } + }, + messageEn: 'Nurse account created successfully.', + messageAr: '.تم إنشاء حساب الممرض بنجاح' + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + ValidationMiddleware(AddUserFromAdminDto), + this.adminController.addNurse, + ); + this.router.get( '/admin/doctors', /* @@ -100,6 +157,51 @@ export class AdminRoute implements Routes { this.adminController.getAllDoctors, ); + this.router.get( + '/admin/nurses', + /* + #swagger.tags = ['Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurses retrieved successfully', + schema: { + data: [ + { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + role: 'NURSE', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + isVerified: true, + hasCompletedProfile: true, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: 'Experienced nurse in ICU', + nationalCardUrl: '', + bonusFileUrl: '' + } + } + ], + messageEn: 'Nurses retrieved successfully', + messageAr: "تم استرجاع بيانات الممرضين بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.ADMIN), + this.adminController.getAllNurses, + ); + this.router.get( '/admin/doctors/unverified', /* diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index 73b9eee..a07010e 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -1,7 +1,7 @@ import { DoctorAccountStatus, NurseAccountStatus, PrismaClient, Role } from '@prisma/client'; import { hash } from 'bcrypt'; import { Service } from 'typedi'; -import { AddDoctorFromAdminDto, DoctorFromAdminResponseDto, NurseFromAdminResponseDto } from '@/dtos/admins.dto'; +import { AddUserFromAdminDto, DoctorFromAdminResponseDto, NurseFromAdminResponseDto } from '@/dtos/admins.dto'; import { HttpException } from '@/exceptions/HttpException'; import { ErrorMessages, createBilingualError } from '@/utils/errorMessages'; import { User } from '@/interfaces'; @@ -14,7 +14,7 @@ const prisma = new PrismaClient(); @Service() export class AdminService { - public async addDoctor(doctorData: AddDoctorFromAdminDto): Promise { + public async addDoctor(doctorData: AddUserFromAdminDto): Promise { // Check if email already exists const existingUser = await prisma.user.findUnique({ where: { email: doctorData.email } @@ -92,6 +92,84 @@ export class AdminService { } + public async addNurse(nurseData: AddUserFromAdminDto): Promise { + const existingUser = await prisma.user.findUnique({ + where: { + email: nurseData.email + } + }); + + if (existingUser) { + const error = createBilingualError(409, ErrorMessages.EMAIL_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const username = nurseData.email.split('@')[0]; + + const existingUsername = await prisma.user.findUnique({ + where: { + username + } + }); + + if (existingUsername) { + const error = createBilingualError(409, ErrorMessages.USERNAME_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const defaultPassword = 'nurse123'; + const hashedPassword = await hash(defaultPassword, 10); + + const createdUser = await prisma.user.create({ + data: { + email: nurseData.email, + name: nurseData.name, + username, + phone: nurseData.phone, + gender: nurseData.gender, + date_of_birth: new Date(nurseData.date_of_birth), + password_hash: hashedPassword, + role: Role.NURSE, + isVerified: true, + hasCompletedProfile: false, + }, + }); + + await prisma.nurse.create({ + data: { + id: createdUser.id, + years_of_experience: nurseData.years_of_experience, + account_status: NurseAccountStatus.APPROVED, + } + }); + const createdNurse = await prisma.user.findUnique({ + where: { + id: createdUser.id + }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + nurse: { + select: { + years_of_experience: true, + } + }, + } + }); + + return createdNurse; + + } + public async getAllDoctors(): Promise { const doctors = await prisma.user.findMany({ @@ -128,6 +206,39 @@ export class AdminService { } + public async getAllNurses(): Promise { + const nurses = await prisma.user.findMany({ + where: { + role: Role.NURSE + }, + select: { + id: true, + name: true, + email: true, + username: true, + phone: true, + gender: true, + date_of_birth: true, + role: true, + isVerified: true, + hasCompletedProfile: true, + photo_url: true, + nurse: { + select: { + account_status: true, + years_of_experience: true, + brief: true, + nationalCardUrl: true, + bonusFileUrl: true, + } + }, + } + }); + + return nurses; + + } + public async getDoctorById(id: string): Promise { const doctor = await prisma.user.findUnique({ diff --git a/src/swagger-output.json b/src/swagger-output.json index d5f23bc..16e90de 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1581,6 +1581,258 @@ } } }, + "/admin/nurses": { + "post": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Nurse data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "years_of_experience": { + "type": "number", + "example": 3 + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Nurse added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": {}, + "nationalCardUrl": {}, + "bonusFileUrl": {} + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse account created successfully." + }, + "messageAr": { + "type": "string", + "example": ".تم إنشاء حساب الممرض بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurses retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": { + "type": "string", + "example": "Experienced nurse in ICU" + }, + "nationalCardUrl": { + "type": "string", + "example": "" + }, + "bonusFileUrl": { + "type": "string", + "example": "" + } + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurses retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرضين بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/admin/doctors/unverified": { "get": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 9989857..235c023 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -72,6 +72,14 @@ export const SuccessResponseMessages = { message_en: "Unverified nurses retrieved successfully.", message_ar: "تم استرجاع بيانات الممرضات غير المعتمدين بنجاح.", }, + NURSE_CREATED: { + message_en: "Nurse created successfully.", + message_ar: "تم إنشاء حساب الممرضة بنجاح.", + }, + NURSES_RETRIEVED: { + message_en: "Nurse retrieved successfully.", + message_ar: "تم استرجاع بيانات الممرضة بنجاح.", + }, // Success messages for Clinics From 668ea6dbf6acd74cf1597db8583d06d5bb0f5413 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 10:48:32 +0200 Subject: [PATCH 168/210] add nurse routes for super admin --- src/routes/nurse.route.ts | 2 +- src/routes/superAdmin.route.ts | 158 +++++++++++++- src/swagger-output.json | 373 ++++++++++++++++++++++++++++++++- 3 files changed, 529 insertions(+), 4 deletions(-) diff --git a/src/routes/nurse.route.ts b/src/routes/nurse.route.ts index fceda3c..e6c5300 100644 --- a/src/routes/nurse.route.ts +++ b/src/routes/nurse.route.ts @@ -520,7 +520,7 @@ export class NurseRoute implements Routes { /* #swagger.path = '/nurses/appointments/{appointmentId}/complete' #swagger.method = 'patch' - #swagger.tags = ['Appointments'] + #swagger.tags = ['Nurses'] #swagger.parameters['Authorization'] = { in: 'cookie', description: 'Bearer token for authentication (must be a nurse)', diff --git a/src/routes/superAdmin.route.ts b/src/routes/superAdmin.route.ts index 2f1c1e1..32de727 100644 --- a/src/routes/superAdmin.route.ts +++ b/src/routes/superAdmin.route.ts @@ -1,6 +1,6 @@ import { AdminController } from "@/controllers/admin.controller"; import { SuperAdminController } from "@/controllers/superAdmin.controller"; -import { AddDoctorFromAdminDto } from "@/dtos/admins.dto"; +import { AddUserFromAdminDto } from "@/dtos/admins.dto"; import { AddAdminFromSuperAdminDto } from "@/dtos/superAdmins.dto"; import { Routes } from "@/interfaces"; import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; @@ -191,10 +191,67 @@ export class SuperAdminRoute implements Routes { AuthMiddleware, RoleMiddleware(Role.SUPER_ADMIN), LanguageMiddleware, - ValidationMiddleware(AddDoctorFromAdminDto), + ValidationMiddleware(AddUserFromAdminDto), this.adminController.addDoctor, ); + this.router.post( + '/super-admin/nurses', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['body'] = { + in: 'body', + description: 'Nurse data', + required: true, + schema: { + $email: 'nurse@example.com', + $name: 'Nurse Jane', + $phone: '1234567890', + $gender: 'MALE or FEMALE', + $date_of_birth: '1995-06-15', + years_of_experience: 3 + } + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[201] = { + description: 'Nurse added successfully', + schema: { + data: { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + role: 'NURSE', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + isVerified: true, + hasCompletedProfile: false, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: null, + nationalCardUrl: null, + bonusFileUrl: null + } + }, + messageEn: 'Nurse account created successfully.', + messageAr: '.تم إنشاء حساب الممرض بنجاح' + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + ValidationMiddleware(AddUserFromAdminDto), + this.adminController.addNurse, + ); + this.router.get( '/super-admin/doctors', /* @@ -232,6 +289,51 @@ export class SuperAdminRoute implements Routes { this.adminController.getAllDoctors, ); + this.router.get( + '/super-admin/nurses', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurses retrieved successfully', + schema: { + data: [ + { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + role: 'NURSE', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + isVerified: true, + hasCompletedProfile: true, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: 'Experienced nurse in ICU', + nationalCardUrl: '', + bonusFileUrl: '' + } + } + ], + messageEn: 'Nurses retrieved successfully', + messageAr: "تم استرجاع بيانات الممرضين بنجاح." + } + } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.adminController.getAllNurses, + ); + this.router.get( '/super-admin/doctors/:id', /* @@ -275,6 +377,58 @@ export class SuperAdminRoute implements Routes { this.adminController.getDoctorById, ) + this.router.get( + '/super-admin/nurses/:id', + /* + #swagger.tags = ['Super Admin'] + #swagger.parameters['id'] = { + in: 'path', + description: 'Nurse ID', + required: true, + type: 'string' + } + #swagger.parameters['Authorization'] = { + in: 'header', + description: 'Bearer access token (sent via Authorization cookie)', + required: false, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurse retrieved successfully', + schema: { + data: { + id: '1', + email: 'nurse@example.com', + name: 'Nurse Jane', + username: 'jane', + phone: '1234567890', + gender: 'FEMALE', + date_of_birth: '1995-06-15', + role: 'NURSE', + isVerified: true, + hasCompletedProfile: true, + photo_url: null, + nurse: { + account_status: 'APPROVED', + years_of_experience: 3, + brief: 'Experienced nurse in ICU', + nationalCardUrl: '', + bonusFileUrl: '' + } + }, + messageEn: 'Nurse retrieved successfully', + messageAr: "تم استرجاع بيانات الممرض بنجاح." + } + } + #swagger.responses[404] = { + description: 'Nurse not found', + } + */ + AuthMiddleware, + RoleMiddleware(Role.SUPER_ADMIN), + this.adminController.getNurseById, + ); + // CLINIC ROUTES this.router.get( '/super-admin/clinics', diff --git a/src/swagger-output.json b/src/swagger-output.json index 16e90de..4343668 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -3278,6 +3278,258 @@ } } }, + "/super-admin/nurses": { + "post": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Nurse data", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "MALE or FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "years_of_experience": { + "type": "number", + "example": 3 + } + }, + "required": [ + "email", + "name", + "phone", + "gender", + "date_of_birth" + ] + } + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Nurse added successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": false + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": {}, + "nationalCardUrl": {}, + "bonusFileUrl": {} + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse account created successfully." + }, + "messageAr": { + "type": "string", + "example": ".تم إنشاء حساب الممرض بنجاح" + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurses retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": { + "type": "string", + "example": "Experienced nurse in ICU" + }, + "nationalCardUrl": { + "type": "string", + "example": "" + }, + "bonusFileUrl": { + "type": "string", + "example": "" + } + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurses retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرضين بنجاح." + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/super-admin/doctors/{id}": { "get": { "tags": [ @@ -3415,6 +3667,125 @@ } } }, + "/super-admin/nurses/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "Nurse ID" + }, + { + "name": "Authorization", + "in": "header", + "description": "Bearer access token (sent via Authorization cookie)", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurse retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "email": { + "type": "string", + "example": "nurse@example.com" + }, + "name": { + "type": "string", + "example": "Nurse Jane" + }, + "username": { + "type": "string", + "example": "jane" + }, + "phone": { + "type": "string", + "example": "1234567890" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "date_of_birth": { + "type": "string", + "example": "1995-06-15" + }, + "role": { + "type": "string", + "example": "NURSE" + }, + "isVerified": { + "type": "boolean", + "example": true + }, + "hasCompletedProfile": { + "type": "boolean", + "example": true + }, + "photo_url": {}, + "nurse": { + "type": "object", + "properties": { + "account_status": { + "type": "string", + "example": "APPROVED" + }, + "years_of_experience": { + "type": "number", + "example": 3 + }, + "brief": { + "type": "string", + "example": "Experienced nurse in ICU" + }, + "nationalCardUrl": { + "type": "string", + "example": "" + }, + "bonusFileUrl": { + "type": "string", + "example": "" + } + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurse retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع بيانات الممرض بنجاح." + } + }, + "xml": { + "name": "main" + } + } + }, + "404": { + "description": "Nurse not found" + } + } + } + }, "/super-admin/clinics": { "get": { "tags": [ @@ -8128,7 +8499,7 @@ "/nurses/appointments/{appointmentId}/complete": { "patch": { "tags": [ - "Appointments" + "Nurses" ], "description": "Mark an appointment as completed. Only accessible by authenticated nurses", "parameters": [ From 5c744aa43808449d9e6104749633007bb8a569ea Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 11:12:14 +0200 Subject: [PATCH 169/210] sort working days --- src/services/nurse.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/services/nurse.service.ts b/src/services/nurse.service.ts index f6e1e62..858a21c 100644 --- a/src/services/nurse.service.ts +++ b/src/services/nurse.service.ts @@ -240,6 +240,9 @@ export class NurseService { is_active: true, deleted_at: null, }, + orderBy: { + day_of_week: 'asc', + }, select: { id: true, doctor: { @@ -363,6 +366,9 @@ export class NurseService { day_of_week: true, start_time: true, end_time: true, + }, + orderBy: { + day_of_week: 'asc', } }, status: true, @@ -449,6 +455,9 @@ export class NurseService { day_of_week: true, start_time: true, end_time: true, + }, + orderBy: { + day_of_week: 'asc', } }, status: true, From 9f442d0c7247bb91062680173718c4c9a2cebfe2 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 20:50:56 +0200 Subject: [PATCH 170/210] fix: check account status for nurse --- src/interfaces/users.interface.ts | 15 ++++++++++++++- src/services/auth.service.ts | 24 ++++++++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/interfaces/users.interface.ts b/src/interfaces/users.interface.ts index cc98830..305763b 100644 --- a/src/interfaces/users.interface.ts +++ b/src/interfaces/users.interface.ts @@ -3,7 +3,7 @@ import { Medication } from './medications.interface'; import { ScanLab } from './scans-labs.interface'; import { ClinicNurse, ClinicDoctor } from './clinics.interface'; import { AuditLog } from './audit-logs.interface'; -import { DoctorAccountStatus, Gender, Role } from '@prisma/client'; +import { DoctorAccountStatus, Gender, Role, NurseAccountStatus } from '@prisma/client'; export interface User { id: string; @@ -23,6 +23,7 @@ export interface User { photo_url?: string; patient?: Patient; doctor?: Partial; + nurse?: Partial; appointments_as_patient?: Appointment[]; appointments_as_doctor?: Appointment[]; medications_as_patient?: Medication[]; @@ -54,6 +55,15 @@ export interface Doctor { clinic_doctors?: ClinicDoctor[]; } +export interface Nurse { + id: string; + account_status: NurseAccountStatus; + years_of_experience: number; + brief?: string; + + user: User; +} + export interface UserLoginData { name: string, email: string, @@ -69,4 +79,7 @@ export interface UserLoginData { specialization: string; account_status: DoctorAccountStatus; } + nurse?: { + account_status: NurseAccountStatus; + } } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 4099a54..62d67fb 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { DoctorAccountStatus, Role } from '@prisma/client'; +import { DoctorAccountStatus, Role , NurseAccountStatus} from '@prisma/client'; import { compare, hash } from 'bcrypt'; import { sign, verify } from 'jsonwebtoken'; import { Service } from 'typedi'; @@ -65,7 +65,10 @@ export class AuthService { { username: userData.emailOrUsername } ] }, - include: { doctor: true } + include: { + doctor: true, + nurse: true, + } }); if (!findUser) { const error = createBilingualError(404, ErrorMessages.USER_NOT_FOUND_CREDENTIALS); @@ -78,8 +81,8 @@ export class AuthService { throw new HttpException(error.status, error.message, error.messageAr); } - const { name, gender, date_of_birth, email, isVerified, username, phone, role, hasCompletedProfile, doctor, photo_url } = findUser; - const patientLoginData: UserLoginData = { + const { name, gender, date_of_birth, email, isVerified, username, phone, role, hasCompletedProfile, doctor, nurse, photo_url } = findUser; + const userLoginData: UserLoginData = { name, email, username, @@ -93,17 +96,26 @@ export class AuthService { doctor: doctor ? { specialization: doctor.specialization, account_status: doctor.account_status + } : undefined, + nurse: nurse ? { + account_status: nurse.account_status } : undefined }; - if (patientLoginData.doctor && patientLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { + + if (userLoginData.doctor && userLoginData.doctor.account_status !== DoctorAccountStatus.APPROVED) { const error = createBilingualError(403, ErrorMessages.DOCTOR_ACCOUNT_NOT_APPROVED); throw new HttpException(error.status, error.message, error.messageAr); } + if (userLoginData.nurse && userLoginData.nurse.account_status !== NurseAccountStatus.APPROVED) { + const error = createBilingualError(403, ErrorMessages.NURSE_ACCOUNT_NOT_APPROVED); + throw new HttpException(error.status, error.message, error.messageAr); + } + const tokenResponse = await this.createTokens(findUser, userData.rememberMe); const cookies = this.createCookies(tokenResponse); - return { cookies, findUser: patientLoginData }; + return { cookies, findUser: userLoginData }; } public async logout(userData: User): Promise { From 659b50a136f5b9f7491b77089405aa1ae9886f20 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 26 Feb 2026 22:37:20 +0200 Subject: [PATCH 171/210] fix: swagger for nurse verification --- src/controllers/admin.controller.ts | 1 - src/prisma/schema.prisma | 90 ++++++++++++++--------------- src/routes/admin.route.ts | 2 +- src/services/admin.service.ts | 2 +- src/swagger-output.json | 4 +- 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 9023040..f83cd4d 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -177,7 +177,6 @@ export class AdminController { } public updateNurseVerificationStatus = async (req: RequestWithLanguage, res: Response, next: NextFunction): Promise => { - const nurseId = req.params.id; const { isVerified } = req.body; await this.adminService.updateNurseVerificationStatus(nurseId, isVerified); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 1732ad5..83e32c5 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -28,6 +28,8 @@ model User { hasCompletedProfile Boolean @default(false) photo_public_id String? @db.VarChar(500) photo_url String? @db.VarChar(500) + announcementNurses AnnouncementNurse[] + announcements Announcement[] appointments_as_doctor Appointment[] @relation("DoctorAppointments") appointments_as_patient Appointment[] @relation("PatientAppointments") audit_logs AuditLog[] @relation("UserAuditLogs") @@ -36,15 +38,13 @@ model User { medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") medications_as_doctor Medication[] @relation("DoctorMedications") medications_as_patient Medication[] @relation("PatientMedications") + nurse Nurse? @relation("UserAsNurse") + nurseSchedules NurseSchedule[] controlled_patients Patient[] @relation("ControllingNurse") patient Patient? @relation("UserAsPatient") refresh_tokens RefreshToken[] @relation("UserRefreshTokens") scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") - nurse Nurse? @relation("UserAsNurse") - nurseSchedules NurseSchedule[] - announcements Announcement[] - announcementNurses AnnouncementNurse[] @@map("Users") } @@ -55,27 +55,27 @@ model Doctor { avg_time DateTime? @db.Time(0) account_status DoctorAccountStatus @default(PENDING) num_of_created_clinics Int @default(0) - graduationCertificateUrl String? @db.VarChar(500) + fellowshipCertificatePublicId String? @db.VarChar(500) + fellowshipCertificateUrl String? @db.VarChar(500) graduationCertificatePublicId String? @db.VarChar(500) - membershipCardUrl String? @db.VarChar(500) + graduationCertificateUrl String? @db.VarChar(500) + mastersCertificatePublicId String? @db.VarChar(500) + mastersCertificateUrl String? @db.VarChar(500) membershipCardPublicId String? @db.VarChar(500) - professionalPracticeCardUrl String? @db.VarChar(500) + membershipCardUrl String? @db.VarChar(500) professionalPracticeCardPublicId String? @db.VarChar(500) - mastersCertificateUrl String? @db.VarChar(500) - mastersCertificatePublicId String? @db.VarChar(500) - fellowshipCertificateUrl String? @db.VarChar(500) - fellowshipCertificatePublicId String? @db.VarChar(500) - unionSpecializationCertificateUrl String? @db.VarChar(500) + professionalPracticeCardUrl String? @db.VarChar(500) unionSpecializationCertificatePublicId String? @db.VarChar(500) + unionSpecializationCertificateUrl String? @db.VarChar(500) availability_type AvailabilityType @default(UNSET) present Boolean @default(true) + announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseDoctor") + announcements Announcement[] @relation("AnnouncementDoctor") clinic_doctors ClinicDoctor[] user User @relation("UserAsDoctor", fields: [id], references: [id], onDelete: Cascade) doctorSchedules DoctorSchedule[] + nurseSchedules NurseSchedule[] @relation("NurseScheduleDoctor") vacations Vacation[] - announcements Announcement[] @relation("AnnouncementDoctor") - announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseDoctor") - nurseSchedules NurseSchedule[] @relation("NurseScheduleDoctor") @@map("Doctor") } @@ -95,14 +95,14 @@ model Nurse { id String @id @default(uuid()) account_status NurseAccountStatus @default(PENDING) years_of_experience Int - nationalCardUrl String? @db.VarChar(500) - nationalCardPublicId String? @db.VarChar(500) - bonusFileUrl String? @db.VarChar(500) - bonusFilePublicId String? @db.VarChar(500) brief String? + bonusFilePublicId String? @db.VarChar(500) + bonusFileUrl String? @db.VarChar(500) + nationalCardPublicId String? @db.VarChar(500) + nationalCardUrl String? @db.VarChar(500) + announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurseNurse") user User @relation("UserAsNurse", fields: [id], references: [id], onDelete: Cascade) nurse_schedules NurseSchedule[] @relation("NurseScheduleNurse") - announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurseNurse") @@map("Nurse") } @@ -114,12 +114,12 @@ model Appointment { scheduled_time DateTime is_online Boolean @default(false) is_completed Boolean @default(false) - position Int @default(0) - patients_ahead Int @default(0) estimated_time Float? created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? + patients_ahead Int @default(0) + position Int @default(0) cancelled_by String? clinic_id String? end_time DateTime @@ -195,13 +195,13 @@ model Clinic { name String @db.VarChar(255) opening_at String @db.VarChar(12) closing_at String @db.VarChar(12) + announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseClinic") + announcements Announcement[] @relation("AnnouncementClinic") appointments Appointment[] clinic_doctors ClinicDoctor[] clinic_nurses ClinicNurse[] doctorSchedules DoctorSchedule[] nurseSchedules NurseSchedule[] - announcements Announcement[] @relation("AnnouncementClinic") - announcementNurses AnnouncementNurse[] @relation("AnnouncementNurseClinic") @@map("Clinic") } @@ -295,9 +295,9 @@ model DoctorSchedule { created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - vacations Vacation[] clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + vacations Vacation[] @@index([doctor_id]) @@index([clinic_id]) @@ -317,11 +317,11 @@ model NurseSchedule { created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - nurse Nurse @relation("NurseScheduleNurse", fields: [nurse_id], references: [id], onDelete: Cascade) - doctor Doctor @relation("NurseScheduleDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) + userId String? clinic Clinic? @relation(fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation("NurseScheduleDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) + nurse Nurse @relation("NurseScheduleNurse", fields: [nurse_id], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) - userId String? @@index([nurse_id]) @@index([doctor_id]) @@ -339,9 +339,8 @@ model Vacation { created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? - - doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) - schedule DoctorSchedule @relation(fields: [schedule_id], references: [id], onDelete: Cascade) + doctor Doctor @relation(fields: [doctor_id], references: [id], onDelete: Cascade) + schedule DoctorSchedule @relation(fields: [schedule_id], references: [id], onDelete: Cascade) @@index([doctor_id]) @@index([schedule_id]) @@ -351,24 +350,23 @@ model Vacation { } model Announcement { - id String @id @default(uuid()) + id String @id @default(uuid()) doctor_id String clinic_id String - status AnnouncementStatus @default(PENDING) + status AnnouncementStatus @default(PENDING) gender Gender? max_age Int? years_of_experience Int? notes String? - created_at DateTime @default(now()) - modified_at DateTime @updatedAt + created_at DateTime @default(now()) + modified_at DateTime @updatedAt deleted_at DateTime? - - doctor Doctor @relation("AnnouncementDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) - clinic Clinic @relation("AnnouncementClinic", fields: [clinic_id], references: [id], onDelete: Cascade) + userId String? working_days AnnouncementDay[] announcement_nurses AnnouncementNurse[] @relation("AnnouncementNurses") + clinic Clinic @relation("AnnouncementClinic", fields: [clinic_id], references: [id], onDelete: Cascade) + doctor Doctor @relation("AnnouncementDoctor", fields: [doctor_id], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) - userId String? @@index([doctor_id]) @@index([clinic_id]) @@ -397,15 +395,13 @@ model AnnouncementNurse { clinic_id String? created_at DateTime @default(now()) modified_at DateTime @updatedAt + userId String? deleted_at DateTime? - - announcement Announcement @relation("AnnouncementNurses", fields: [announcement_id], references: [id], onDelete: Cascade) - - nurse Nurse @relation("AnnouncementNurseNurse", fields: [nurse_id], references: [id], onDelete: Cascade) - doctor Doctor? @relation("AnnouncementNurseDoctor", fields: [doctor_id], references: [id]) - clinic Clinic? @relation("AnnouncementNurseClinic", fields: [clinic_id], references: [id]) - user User? @relation(fields: [userId], references: [id]) - userId String? + announcement Announcement @relation("AnnouncementNurses", fields: [announcement_id], references: [id], onDelete: Cascade) + clinic Clinic? @relation("AnnouncementNurseClinic", fields: [clinic_id], references: [id]) + doctor Doctor? @relation("AnnouncementNurseDoctor", fields: [doctor_id], references: [id]) + nurse Nurse @relation("AnnouncementNurseNurse", fields: [nurse_id], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id]) @@unique([announcement_id, nurse_id]) @@index([announcement_id]) diff --git a/src/routes/admin.route.ts b/src/routes/admin.route.ts index 3911205..d55cd4e 100644 --- a/src/routes/admin.route.ts +++ b/src/routes/admin.route.ts @@ -343,7 +343,7 @@ export class AdminRoute implements Routes { description: 'Verification status', required: true, schema: { - $isApproved: true + $isVerified: true } } #swagger.parameters['Authorization'] = { diff --git a/src/services/admin.service.ts b/src/services/admin.service.ts index a07010e..798d6b9 100644 --- a/src/services/admin.service.ts +++ b/src/services/admin.service.ts @@ -425,7 +425,7 @@ export class AdminService { const mailOptions = { from: SENDER_EMAIL, to: user.email, - subject: isApproved ? 'Doctor Account Approved - MedBridge' : 'Doctor Account Rejected - MedBridge', + subject: isApproved ? 'User Account Approved - HoloCura' : 'User Account Rejected - HoloCura', html: `

Dear ${user.name},

Your account has been ${isApproved ? 'approved' : 'rejected'}.

diff --git a/src/swagger-output.json b/src/swagger-output.json index 4343668..a344eed 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -2166,13 +2166,13 @@ "schema": { "type": "object", "properties": { - "isApproved": { + "isVerified": { "type": "boolean", "example": true } }, "required": [ - "isApproved" + "isVerified" ] } }, From 51b78620ef76435d164006c5e5a1b991e1e9becb Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 28 Feb 2026 17:19:02 +0200 Subject: [PATCH 172/210] update complete appointment by nurse --- src/services/appointment.service.ts | 19 +++++++++++++++++++ src/utils/errorMessages.ts | 8 ++++++++ 2 files changed, 27 insertions(+) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 3ee79d8..c155f34 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -909,11 +909,30 @@ export class AppointmentService { }, select: { doctor_id: true, + scheduled_time: true, + status: true, } }); await this.getAndValidateAppointment(appointmentId, appointment.doctor_id); + if (appointment.status === 'COMPLETED') { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ALREADY_COMPLETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const nowUTC = new Date(); + const egyptOffset = 2 * 60 * 60 * 1000; + const now = new Date(nowUTC.getTime() + egyptOffset); + + console.log('Current time in Egypt:', now); + console.log('Appointment scheduled time:', appointment.scheduled_time); + + if (now < appointment.scheduled_time) { + const error = createBilingualError(400, ErrorMessages.CANNOT_BE_COMPLETED_BEFORE_SCHEDULED_TIME); + throw new HttpException(error.status, error.message, error.messageAr); + } + await prisma.appointment.update({ where: { id: appointmentId, diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index a41b38a..7e439c9 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -313,6 +313,14 @@ export const ErrorMessages = { en: "Please choose either online or offline", ar: "يرجى اختيار إما الإلكتروني أو الحضوري" }, + APPOINTMENT_ALREADY_COMPLETED: { + en: 'Appointment is already completed', + ar: 'الموعد مكتمل بالفعل', + }, + CANNOT_BE_COMPLETED_BEFORE_SCHEDULED_TIME: { + en: "Appointment cannot be completed before its scheduled time", + ar: "لا يمكن إكمال الموعد قبل وقته المحدد" + }, // Generic errors SOMETHING_WENT_WRONG: { en: 'Something went wrong', From 97d53bfd66bd18468d7a8d08faf8ac35d4a4181c Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 2 Mar 2026 00:30:29 +0200 Subject: [PATCH 173/210] update schema for medical records and encryption --- package-lock.json | 188 ++++++++++++++++++ package.json | 1 + .../migration.sql | 55 +++++ src/prisma/schema.prisma | 53 +++-- src/services/ipfs.service.ts | 4 +- 5 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 src/prisma/migrations/20260301221257_update_medical_records_and_encryption/migration.sql diff --git a/package-lock.json b/package-lock.json index 8270fa4..d7e4be5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "pinata": "^1.10.1", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.3", @@ -2419,6 +2420,45 @@ "multiformats": "^13.0.0" } }, + "node_modules/@multiformats/mafmt": { + "version": "12.1.6", + "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", + "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + } + }, + "node_modules/@multiformats/mafmt/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@multiformats/mafmt/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/mafmt/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/@multiformats/multiaddr": { "version": "11.6.1", "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-11.6.1.tgz", @@ -7139,6 +7179,29 @@ "version": "4.2.3", "license": "MIT" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fflate": { "version": "0.8.2", "dev": true, @@ -7367,6 +7430,18 @@ "node": ">= 0.6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "3.5.4", "dev": true, @@ -8395,6 +8470,53 @@ "node": ">=0.10.0" } }, + "node_modules/is-ipfs": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/is-ipfs/-/is-ipfs-8.0.4.tgz", + "integrity": "sha512-upkO6a8WgBSZMMmuPzmF2NQLWXtiJtHxdEfEiMWrOzCKoZ+XEiM0XlK4fFMfo/PyiRmPMJ4PsNrXyvJeqMrJXA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/mafmt": "^12.1.6", + "@multiformats/multiaddr": "^12.1.14", + "iso-url": "^1.1.3", + "multiformats": "^13.0.1", + "uint8arrays": "^5.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/is-ipfs/node_modules/@multiformats/multiaddr": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", + "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "abort-error": "^1.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/is-ipfs/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/is-ipfs/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -10299,6 +10421,26 @@ "node": ">=6.0.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -11064,6 +11206,43 @@ "node": ">=10" } }, + "node_modules/pinata": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/pinata/-/pinata-1.10.1.tgz", + "integrity": "sha512-/nB9C7zCEnC5YW+deO5FGX5RizSN23JXvbUdDLfzgwtKj4m5JC8ScuLkQ+iirfvpPQGQFQ/X5mR3aC/npTjFDA==", + "license": "MIT", + "dependencies": { + "is-ipfs": "^8.0.4", + "node-fetch": "^3.3.1" + } + }, + "node_modules/pinata/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/pinata/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/pirates": { "version": "4.0.7", "dev": true, @@ -13834,6 +14013,15 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index f396aa5..9b6de46 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "nodemailer": "^7.0.10", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", + "pinata": "^1.10.1", "prisma": "6.18.0", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.3", diff --git a/src/prisma/migrations/20260301221257_update_medical_records_and_encryption/migration.sql b/src/prisma/migrations/20260301221257_update_medical_records_and_encryption/migration.sql new file mode 100644 index 0000000..daf6cd5 --- /dev/null +++ b/src/prisma/migrations/20260301221257_update_medical_records_and_encryption/migration.sql @@ -0,0 +1,55 @@ +/* + Warnings: + + - Added the required column `clinic_id` to the `MedicalRecords` table without a default value. This is not possible if the table is not empty. + - Added the required column `key_id` to the `MedicalRecords` table without a default value. This is not possible if the table is not empty. + - Added the required column `mime_type` to the `MedicalRecords` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "RecordType" ADD VALUE 'SOAP_NOTE'; +ALTER TYPE "RecordType" ADD VALUE 'MEDICAL_HISTORY'; + +-- AlterTable +ALTER TABLE "MedicalRecords" ADD COLUMN "appointment_id" TEXT, +ADD COLUMN "clinic_id" TEXT NOT NULL, +ADD COLUMN "key_id" TEXT NOT NULL, +ADD COLUMN "mime_type" VARCHAR(100) NOT NULL; + +-- CreateTable +CREATE TABLE "EncryptionKeys" ( + "id" TEXT NOT NULL, + "patient_id" TEXT NOT NULL, + "encrypted_key" VARCHAR(500) NOT NULL, + "algorithm" VARCHAR(50) NOT NULL DEFAULT 'AES-256-GCM', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" TIMESTAMP(3) NOT NULL, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "EncryptionKeys_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "EncryptionKeys_patient_id_key" ON "EncryptionKeys"("patient_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_clinic_id_idx" ON "MedicalRecords"("clinic_id"); + +-- CreateIndex +CREATE INDEX "MedicalRecords_appointment_id_idx" ON "MedicalRecords"("appointment_id"); + +-- AddForeignKey +ALTER TABLE "MedicalRecords" ADD CONSTRAINT "MedicalRecords_appointment_id_fkey" FOREIGN KEY ("appointment_id") REFERENCES "Appointments"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MedicalRecords" ADD CONSTRAINT "MedicalRecords_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "EncryptionKeys"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EncryptionKeys" ADD CONSTRAINT "EncryptionKeys_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 83e32c5..ace7e33 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -28,6 +28,7 @@ model User { hasCompletedProfile Boolean @default(false) photo_public_id String? @db.VarChar(500) photo_url String? @db.VarChar(500) + announcementNurses AnnouncementNurse[] announcements Announcement[] appointments_as_doctor Appointment[] @relation("DoctorAppointments") @@ -35,7 +36,6 @@ model User { audit_logs AuditLog[] @relation("UserAuditLogs") clinics_as_nurse ClinicNurse[] @relation("NurseClinics") doctor Doctor? @relation("UserAsDoctor") - medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") medications_as_doctor Medication[] @relation("DoctorMedications") medications_as_patient Medication[] @relation("PatientMedications") nurse Nurse? @relation("UserAsNurse") @@ -45,6 +45,8 @@ model User { refresh_tokens RefreshToken[] @relation("UserRefreshTokens") scans_labs_as_doctor ScanLab[] @relation("DoctorScansLabs") scans_labs_as_patient ScanLab[] @relation("PatientScansLabs") + medical_records_as_patient MedicalRecord[] @relation("PatientMedicalRecords") + encryption_key EncryptionKey? @relation("PatientEncryptionKey") @@map("Users") } @@ -128,6 +130,7 @@ model Appointment { clinic Clinic? @relation(fields: [clinic_id], references: [id]) doctor User? @relation("DoctorAppointments", fields: [doctor_id], references: [id], onDelete: Restrict) patient User? @relation("PatientAppointments", fields: [patient_id], references: [id], onDelete: Restrict) + medicalRecords MedicalRecord[] @@index([patient_id]) @@index([doctor_id]) @@ -246,23 +249,47 @@ model AuditLog { } model MedicalRecord { - id String @id @default(uuid()) - patient_id String - doctor_id String? - name String @db.VarChar(255) - cid String @unique @db.VarChar(255) - type RecordType - created_at DateTime @default(now()) - modified_at DateTime @updatedAt - deleted_at DateTime? - patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id]) + id String @id @default(uuid()) + patient_id String + clinic_id String + doctor_id String? + appointment_id String? + name String @db.VarChar(255) + cid String @unique @db.VarChar(255) + mime_type String @db.VarChar(100) + type RecordType + key_id String + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id]) + appointment Appointment? @relation(fields: [appointment_id], references: [id]) + encryption_key EncryptionKey @relation(fields: [key_id], references: [id]) @@index([patient_id]) - @@index([doctor_id]) + @@index([clinic_id]) + @@index([doctor_id]) + @@index([appointment_id]) @@index([cid]) @@map("MedicalRecords") } +model EncryptionKey { + id String @id @default(uuid()) + patient_id String @unique + encrypted_key String @db.VarChar(500) + algorithm String @default("AES-256-GCM") @db.VarChar(50) + created_at DateTime @default(now()) + modified_at DateTime @updatedAt + deleted_at DateTime? + + patient User @relation("PatientEncryptionKey", fields: [patient_id], references: [id], onDelete: Cascade) + medical_records MedicalRecord[] + + @@map("EncryptionKeys") +} + model RefreshToken { id String @id @default(uuid()) user_id String @@ -456,6 +483,8 @@ enum RecordType { SCAN DIAGNOSIS VISIT_SUMMARY + SOAP_NOTE + MEDICAL_HISTORY } enum DoctorAccountStatus { diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index 9911aa8..adcbe11 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -1,10 +1,10 @@ -import { promises } from 'dns'; -import { port } from 'envalid' +import { PinataSDK } from 'pinata'; import { create, IPFSHTTPClient } from 'ipfs-http-client' import { HttpException } from '@/exceptions/HttpException'; import { Service } from 'typedi'; + @Service() export class IpfsService { private ipfsClient: IPFSHTTPClient; From 186ae4f17dac944face0379ce46daec64db3e321 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 3 Mar 2026 09:20:36 +0200 Subject: [PATCH 174/210] add encryption/key management services --- src/services/encryption.service.ts | 82 ++++++++++++++++++++++++++ src/services/key-management.service.ts | 52 ++++++++++++++++ src/utils/errorMessages.ts | 18 ++++++ 3 files changed, 152 insertions(+) create mode 100644 src/services/encryption.service.ts create mode 100644 src/services/key-management.service.ts diff --git a/src/services/encryption.service.ts b/src/services/encryption.service.ts new file mode 100644 index 0000000..1b6d4fb --- /dev/null +++ b/src/services/encryption.service.ts @@ -0,0 +1,82 @@ +import { Service } from 'typedi'; +import * as crypto from 'crypto'; +import { HttpException } from '@/exceptions/HttpException'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; + + +@Service() +export class EncryptionService { + private readonly algorithm = 'aes-256-gcm'; + private readonly ivLength = 12; + private readonly tagLength = 16; + private readonly keyLength = 32; + + + public encryptFile(fileBuffer: Buffer, dek: Buffer): Buffer { + const iv = crypto.randomBytes(this.ivLength); + const cipher = crypto.createCipheriv(this.algorithm, dek, iv); + + const tag = cipher.getAuthTag(); + const encryptedData = Buffer.concat([cipher.update(fileBuffer), cipher.final()]); + + return Buffer.concat([iv, tag, encryptedData]) + } + + public decryptFile(encryptedFile: Buffer, dek: Buffer): Buffer { + const iv = encryptedFile.subarray(0, this.ivLength); + const tag = encryptedFile.subarray(this.ivLength, this.ivLength + this.tagLength); + const encryptedData = encryptedFile.subarray(this.ivLength + this.tagLength); + + const decipher = crypto.createDecipheriv(this.algorithm, dek, iv); + decipher.setAuthTag(tag); + + return Buffer.concat([decipher.update(encryptedData), decipher.final()]); + + } + + public encryptDEK(dek: Buffer): string { + const masterKey = this.getMasterKey(); + const iv = crypto.randomBytes(this.ivLength); + + const cipher = crypto.createCipheriv(this.algorithm, masterKey, iv); + const tag = cipher.getAuthTag(); + + const encryptedData = Buffer.concat([cipher.update(dek), cipher.final()]); + return Buffer.concat([iv, tag, encryptedData]).toString('hex'); + + } + + public decryptDEK(encryptedDEK: string): Buffer { + const masterKey = this.getMasterKey(); + const data = Buffer.from(encryptedDEK, 'hex'); + + const iv = data.subarray(0, this.ivLength); + const tag = data.subarray(this.ivLength, this.ivLength + this.tagLength); + const encryptedData = data.subarray(this.ivLength + this.tagLength); + + const decipher = crypto.createDecipheriv(this.algorithm, masterKey, iv); + decipher.setAuthTag(tag); + + return Buffer.concat([decipher.update(encryptedData), decipher.final()]); + + } + + + public generateDEK(): Buffer { + return crypto.randomBytes(this.keyLength); + } + + private getMasterKey(): Buffer { + const masterKey = process.env.MASTER_ENCRYPTION_KEY; + if (!masterKey) { + const error = createBilingualError(500, ErrorMessages.MASTER_KEY_NOT_SET); + throw new HttpException(error.status, error.message, error.messageAr); + } + const keyBuffer = Buffer.from(masterKey, 'hex'); + if (keyBuffer.length !== this.keyLength) { + const error = createBilingualError(500, ErrorMessages.INVALID_MASTER_KEY_LENGTH); + throw new HttpException(error.status, error.message, error.messageAr); + } + return Buffer.from(masterKey, 'hex'); + } +} \ No newline at end of file diff --git a/src/services/key-management.service.ts b/src/services/key-management.service.ts new file mode 100644 index 0000000..51f81e6 --- /dev/null +++ b/src/services/key-management.service.ts @@ -0,0 +1,52 @@ +import { Service } from 'typedi'; +import prisma from '@/config/prisma'; +import { EncryptionService } from './encryption.service'; +import { HttpException } from '@/exceptions/HttpException'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; + +@Service() +export class KeyManagementService { + + private encryptionService = new EncryptionService(); + + public async createPatientKey(patientId: string): Promise { + const existing = await prisma.encryptionKey.findUnique({ + where: { + patient_id: patientId + } + }); + if (existing){ + const error = createBilingualError(400, ErrorMessages.PATIENT_KEY_ALREADY_EXISTS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const patientDEK = this.encryptionService.generateDEK(); + const encryptedDEK = this.encryptionService.encryptDEK(patientDEK); + + await prisma.encryptionKey.create({ + data: { + patient_id: patientId, + encrypted_key: encryptedDEK, + algorithm: 'AES-256-GCM', + } + }); + patientDEK.fill(0); + } + + public async getPatientDEK(patientId: string): Promise { + const keyRecord = await prisma.encryptionKey.findUnique({ + where: { + patient_id: patientId + } + }); + + if (!keyRecord) { + const error = createBilingualError(404, ErrorMessages.PATIENT_KEY_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + + } + + return this.encryptionService.decryptDEK(keyRecord.encrypted_key); + } + +} diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 7e439c9..eba9f13 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -93,6 +93,16 @@ export const ErrorMessages = { ar: 'خطأ في المصادقة عبر Google', }, + // patient + PATIENT_KEY_NOT_FOUND: { + en: 'Patient key not found', + ar: 'مفتاح المريض غير موجود', + }, + PATIENT_KEY_ALREADY_EXISTS: { + en: 'Patient key already exists', + ar: 'مفتاح المريض موجود بالفعل', + }, + //Doctor specific errors DOCTOR_ACCOUNT_NOT_APPROVED: { en: 'Doctor account is not approved yet', @@ -326,6 +336,14 @@ export const ErrorMessages = { en: 'Something went wrong', ar: 'حدث خطأ ما', }, + MASTER_KEY_NOT_SET: { + en: 'Master encryption key is not set in environment variables', + ar: 'مفتاح التشفير الرئيسي غير مضبوط', + }, + INVALID_MASTER_KEY_LENGTH: { + en: 'Invalid master key length. Expected 32 bytes', + ar: 'طول مفتاح رئيسي غير صالح. يجب أن يكون 32 بايت', + }, APPOINTMENT_NOT_FOUND: { en: "Appointment not found", From 608fd7c6676f6bca7c9d4624fdd4a14632da3ef0 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Tue, 3 Mar 2026 17:25:46 +0200 Subject: [PATCH 175/210] update ipfs/MR services --- -home-enjy-work-GP-repo-Backend-src-dtos-.txt | 1 + src/dtos/medicalRecord.dto.ts | 66 +++------- src/interfaces/medicalRecords.interface.ts | 8 +- src/services/ipfs.service.ts | 65 +++++----- src/services/medical-records.service.ts | 114 ++++++++++-------- src/utils/errorMessages.ts | 8 ++ 6 files changed, 123 insertions(+), 139 deletions(-) create mode 100644 -home-enjy-work-GP-repo-Backend-src-dtos-.txt diff --git a/-home-enjy-work-GP-repo-Backend-src-dtos-.txt b/-home-enjy-work-GP-repo-Backend-src-dtos-.txt new file mode 100644 index 0000000..f44bfdd --- /dev/null +++ b/-home-enjy-work-GP-repo-Backend-src-dtos-.txt @@ -0,0 +1 @@ +/home/enjy/work/GP/repo/Backend/src/dtos/appointments.dto.ts \ No newline at end of file diff --git a/src/dtos/medicalRecord.dto.ts b/src/dtos/medicalRecord.dto.ts index 10fef82..becd600 100644 --- a/src/dtos/medicalRecord.dto.ts +++ b/src/dtos/medicalRecord.dto.ts @@ -1,63 +1,25 @@ -import { IsDateString, IsOptional, IsString, Length } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsDateString, IsOptional, IsUUID, IsNumber, ValidateIf, IsString, Min, IsInt, Max } from 'class-validator'; +import { RecordType } from '@prisma/client'; export class CreateMedicalRecordDto { - @IsString() - @Length(3, 64) - public patientId: string; - @IsString() - @Length(1, 64) - public firstName: string; + @IsString() + @IsNotEmpty() + public name: string; - @IsString() - @Length(1, 64) - public lastName: string; + @IsEnum(RecordType) + @IsNotEmpty() + public type: RecordType; - @IsDateString() - public dateOfBirth: string; + @IsUUID() + @IsNotEmpty() + public clinicId: string; - @IsString() - @Length(1, 32) - public gender: string; - - @IsString() - @Length(1, 8) - public bloodType: string; - - @IsString() - @Length(10, 128) - public ipfsCid: string; - - @IsOptional() - @IsString() - public summary?: string; + @IsUUID() + @IsOptional() + public appointmentId?: string; } export class UpdateMedicalRecordDto { - @IsString() - @Length(1, 64) - public firstName: string; - - @IsString() - @Length(1, 64) - public lastName: string; - - @IsDateString() - public dateOfBirth: string; - - @IsString() - @Length(1, 32) - public gender: string; - - @IsString() - @Length(1, 8) - public bloodType: string; - - @IsString() - @Length(10, 128) - public ipfsCid: string; - @IsOptional() - @IsString() - public summary?: string; } diff --git a/src/interfaces/medicalRecords.interface.ts b/src/interfaces/medicalRecords.interface.ts index 559a9a2..3ec93df 100644 --- a/src/interfaces/medicalRecords.interface.ts +++ b/src/interfaces/medicalRecords.interface.ts @@ -4,14 +4,12 @@ import { RecordType } from '@prisma/client'; export interface MedicalRecord { id: string; patient_id: string; + clinic_id: string; doctor_id?: string; + appointment_id?: string; name: string; cid: string; type: RecordType; - created_at: Date; - modified_at: Date; - deleted_at?: Date; - - patient?: User; + mime_type: string; } diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index adcbe11..43cb70e 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -1,46 +1,53 @@ import { PinataSDK } from 'pinata'; -import { create, IPFSHTTPClient } from 'ipfs-http-client' import { HttpException } from '@/exceptions/HttpException'; import { Service } from 'typedi'; - - @Service() export class IpfsService { - private ipfsClient: IPFSHTTPClient; + private pinata: PinataSDK; constructor() { - // temp --> selecting the pinning service (4EVERLAND) - this.ipfsClient = create({ - host: process.env.IPFS_HOST, - port: parseInt(process.env.IPFS_PORT), - protocol: process.env.IPFS_PROTOCOL + this.pinata = new PinataSDK({ + pinataJwt: process.env.PINATA_JWT, + pinataGateway: process.env.PINATA_GATEWAY, }); } - // upload med file to IPFS --> generate and return CID - public async uploadFile(fileData: Buffer, fileName: string): Promise { - const result = await this.ipfsClient.add({ - path: fileName, - content: fileData, - }); - const cid = result.cid.toString(); - return cid - }; + public async uploadFile(fileData: Buffer, fileName: string, mimeType: string): Promise { + try { + const file = new File([fileData], fileName, { type: mimeType }); + const upload = await this.pinata.upload.file(file); + return upload.cid; + } + catch (e) { + throw new HttpException(500, `IPFS upload failed: ${e.message}`); + } + } + - // get file using CID public async getFile(cid: string): Promise { - // note --> each chunk in ipfs is Uint8Array - const chunks: Uint8Array[] = []; - - for await (const chunk of this.ipfsClient.cat(cid)) { - chunks.push(chunk); + try { + // CID → gateway URL → HTTP request → raw bytes stream → read all bytes → Buffer + const url = `https://${process.env.PINATA_GATEWAY}/ipfs/${cid}`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Gateway responded with ${response.status}`); + } + + const arrayBuffer = await response.arrayBuffer(); + return Buffer.from(arrayBuffer); + } catch (e) { + throw new HttpException(500, `IPFS fetch failed: ${e.message}`); } - const fileData = Buffer.concat(chunks); - return fileData; } - // pin management --> TBD - - + public async deleteFile(cid: string): Promise { + try { + await this.pinata.files.delete([cid]); + } + catch (e) { + throw new HttpException(500, `IPFS delete failed: ${e.message}`); + } + } } \ No newline at end of file diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 0db491f..a458ff5 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -1,106 +1,114 @@ -import { PrismaClient } from '@prisma/client'; import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; -import { MedicalRecord } from '@/interfaces/medicalRecords.interface'; import prisma from '@/config/prisma'; import { Service } from 'typedi'; import { IpfsService } from '@/services/ipfs.service'; +import { EncryptionService } from '@/services/encryption.service'; +import { KeyManagementService } from '@/services/key-management.service'; +import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; + @Service() export class MedicalRecordService { - constructor(private ipfsService: IpfsService) { } + private ipfsService = new IpfsService(); + private encryptionService = new EncryptionService(); + private keyManagementService = new KeyManagementService(); + // create a new MR public async createMedicalRecord( patientId: string, + doctorId: string, fileData: CreateMedicalRecordDto, fileBuffer: Buffer, fileName: string, - ): Promise { - // upload to IPFS and get cid - const cid = await this.ipfsService.uploadFile(fileBuffer, fileName); - console.log(`file is uploaded to ipfs, cid:" ${cid}`) + mimeType: string, + ): Promise { + const patientDEK = await this.keyManagementService.getPatientDEK(patientId); + const encryptedFile = this.encryptionService.encryptFile(fileBuffer, patientDEK); + patientDEK.fill(0); - // blockchain stuff + const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + + const keyRecord = await prisma.encryptionKey.findUnique({ + where: { + patient_id: patientId + }, + select: { + id: true + }, + }); // save to db - const medicalRecord = await prisma.medicalRecord.create({ + await prisma.medicalRecord.create({ data: { patient_id: patientId, - doctor_id: fileData.doctor_id || null, + doctor_id: doctorId, + clinic_id: (fileData as any).clinicId, + appointment_id: (fileData as any).appointmentId, name: fileData.name, cid: cid, type: fileData.type, - }, - include: { - patient: true, + mime_type: mimeType, + key_id: keyRecord.id, }, }); - return medicalRecord; } - // get all medical records for a specific patient - - public async getPatientRecords(patientId: string): Promise { - const records = await prisma.medicalRecord.findMany({ + public async getRecordFile(recordId: string): Promise<{ buffer: Buffer; mimeType: string; name: string }> { + const record = await prisma.medicalRecord.findFirst({ where: { - patient_id: patientId, - deleted_at: null, - }, - orderBy: { - created_at: 'desc', - }, - include: { - patient: true, - }, + id: recordId, + deleted_at: null + } }); - return records; - } + if (!record) { + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } - // get MR shared with a doctor - public async getDoctorRecords(doctorId: string): Promise { - const records = await prisma.medicalRecord.findMany({ - where: { - doctor_id: doctorId, - deleted_at: null, - }, - orderBy: { - created_at: 'desc', - }, - include: { - patient: true, - }, - }); + const encryptedFile = await this.ipfsService.getFile(record.cid); + + const patientDEK = await this.keyManagementService.getPatientDEK(record.patient_id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, patientDEK); + patientDEK.fill(0); - return records; + return { + buffer: decryptedFile, + mimeType: record.mime_type, + name: record.name, + }; } // delete any MR (soft) - public async deleteRecord(recordId: string) { - // checking if it's already deleted + public async deleteRecord(recordId: string): Promise { const record = await prisma.medicalRecord.findFirst({ where: { id: recordId, - deleted_at: null, + deleted_at: null }, }); + if (!record) { - throw new HttpException(404, 'medical record not found or already deleted'); + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (record.deleted_at) { + const error = createBilingualError(404, ErrorMessages.RECORD_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); } await prisma.medicalRecord.update({ where: { - id: recordId, + id: recordId }, data: { - deleted_at: new Date(), + deleted_at: new Date() }, }); } - // get a specific MR by id?? - // handle permissions --> fabric stuff - } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index eba9f13..4e37e54 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -216,6 +216,14 @@ export const ErrorMessages = { en: "Language must be 'en' or 'ar'", ar: "يجب أن تكون اللغة 'en' أو 'ar'" }, + RECORD_NOT_FOUND: { + en: 'Record not found', + ar: 'السجل غير موجود', + }, + RECORD_ALREADY_DELETED: { + en: 'Record has already been deleted', + ar: 'تم حذف السجل بالفعل', + }, // Clinic errors CLINIC_NOT_FOUND: { From 405e3909e599eb6e0eba7861e7553404cdafc470 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Tue, 3 Mar 2026 23:33:44 +0200 Subject: [PATCH 176/210] get agora token endpoint --- package-lock.json | 65 +++++++++++++++++++++++ package.json | 1 + src/controllers/appointment.controller.ts | 12 +++++ src/routes/appointment.route.ts | 25 +++++++++ src/services/appointment.service.ts | 30 +++++++++++ src/utils/errorMessages.ts | 10 +++- src/utils/responseMessages.ts | 6 +++ 7 files changed, 147 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8270fa4..187369f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "6.18.0", + "agora-token": "^2.0.5", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", @@ -4638,6 +4639,17 @@ "node": ">= 14" } }, + "node_modules/agora-token": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/agora-token/-/agora-token-2.0.5.tgz", + "integrity": "sha512-0NcbzC3iuutlksv3b4bCMKHrW3pko6gdiGEMRo6APDice24kfXAuWyAlG9hRBrrPBVDShwm9/GUz2Scd3zuZQw==", + "license": "ISC", + "dependencies": { + "crc-32": "^1.2.0", + "cuint": "0.2.2", + "md5": "^2.3.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -5437,6 +5449,15 @@ "node": ">=10" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/charm": { "version": "0.1.2", "dev": true, @@ -5886,6 +5907,18 @@ "node": ">= 0.10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-require": { "version": "1.1.1", "dev": true, @@ -5925,6 +5958,21 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "license": "MIT" + }, "node_modules/culvert": { "version": "0.1.2", "dev": true, @@ -8334,6 +8382,12 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "dev": true, @@ -9712,6 +9766,17 @@ "node": ">= 0.4" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "1.1.0", "license": "MIT", diff --git a/package.json b/package.json index f396aa5..727705f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "6.18.0", + "agora-token": "^2.0.5", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 380058e..21543e9 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -496,4 +496,16 @@ export class AppointmentController { ...response }); }); + + public getAgoraToken = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const userId = req.user?.id; + const { appointmentId } = req.params; + + const token = await this.appointmentService.generateAgoraToken(appointmentId, userId); + const response = createMultiLangMessage(SuccessResponseMessages.AGORA_TOKEN_GENERATED_SUCCESSFULLY); + res.status(200).json({ + ...response, + data: token + }); + }); } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e1bda69..a7b030a 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -16,6 +16,7 @@ export class AppointmentRoute implements Routes { constructor() { this.initializeRoutes(); + // this.router.use() } private initializeRoutes() { @@ -1297,5 +1298,29 @@ export class AppointmentRoute implements Routes { AuthMiddleware, this.appointmentController.cancelDoctorVacation ); + + this.router.get( + `${this.path}/:appointmentId/agora-token`, + /* + #swagger.path = '/appointments/{appointmentId}/agora-token' + #swagger.method = 'get' + #swagger.tags = ['Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication (patient or doctor of the appointment)', + required: true, + type: 'string' + } + #swagger.description = 'Get Agora token and channel name for a specific appointment' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'The ID of the appointment', + required: true, + type: 'string' + } + */ + AuthMiddleware, + this.appointmentController.getAgoraToken + ); } } diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index c155f34..e4170c1 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -8,6 +8,7 @@ import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations, AppointmentData } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; import { start } from 'repl'; +import { RtcRole, RtcTokenBuilder } from 'agora-token'; @Service() export class AppointmentService { @@ -1651,4 +1652,33 @@ export class AppointmentService { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } + public async generateAgoraToken(appointmentId: string, userId: string): Promise { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + } + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const appId = process.env.AGORA_APP_ID; + const appCertificate = process.env.AGORA_APP_CERTIFICATE; + + if (!appId || !appCertificate) { + const error = createBilingualError(500, ErrorMessages.AGORA_CREDENTIALS_NOT_CONFIGURED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const channelName = appointmentId; + const role = RtcRole.PUBLISHER; + const expirationTimeInSeconds = 3600; // 1 hour + const currentTimestamp = Math.floor(Date.now() / 1000); + const privilegeExpiredTs = currentTimestamp + expirationTimeInSeconds; + + const token = RtcTokenBuilder.buildTokenWithUserAccount(appId, appCertificate, channelName, userId, role, privilegeExpiredTs, privilegeExpiredTs); + return token; + } } \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 7e439c9..8a47148 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -110,7 +110,7 @@ export const ErrorMessages = { en: 'Announcement not found', ar: 'الإعلان غير موجود', }, - UNAUTHORIZED_ACCESS :{ + UNAUTHORIZED_ACCESS: { en: 'You are not authorized to access this', ar: 'ليس لديك صلاحية للوصول إلى هذا ', }, @@ -156,7 +156,7 @@ export const ErrorMessages = { en: 'Nurse data not found', ar: 'بيانات الممرضة غير موجودة', }, - + // File upload errors NO_FILE_UPLOADED: { en: 'No file uploaded', @@ -346,6 +346,12 @@ export const ErrorMessages = { INVALID_RESCHEDULE_PARAMETERS: { en: "Provide either new scheduled time or shift minutes", ar: "يرجى تقديم وقت موعد جديد أو عدد دقائق التغيير" + }, + + // Agora Errors + AGORA_CREDENTIALS_NOT_CONFIGURED: { + en: "Agora credentials are not configured", + ar: "بيانات اعتماد Agora غير مكونة" } }; diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 235c023..84c1e8b 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -315,6 +315,12 @@ export const SuccessResponseMessages = { message_ar: 'تم إكمال الموعد بنجاح', }, + // Agora success messages + AGORA_TOKEN_GENERATED_SUCCESSFULLY: { + message_en: "Agora token generated successfully.", + message_ar: "تم إنشاء رمز Agora بنجاح.", + }, + } interface MultiLangMessageObj { From 3fce071d1908360ed5ac467f3e2e1f82ce987751 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 4 Mar 2026 01:07:56 +0200 Subject: [PATCH 177/210] add controller/routes (testing) --- src/controllers/medical-records.controller.ts | 66 +++--- src/dtos/medicalRecord.dto.ts | 8 +- src/interfaces/medicalRecords.interface.ts | 22 +- src/middlewares/upload.middleware.ts | 1 + src/routes/medical-record.route.ts | 224 ++++++++++++++++++ src/server.ts | 3 +- src/services/encryption.service.ts | 4 +- src/services/ipfs.service.ts | 3 +- src/services/key-management.service.ts | 7 +- src/services/medical-records.service.ts | 71 +++++- 10 files changed, 346 insertions(+), 63 deletions(-) create mode 100644 src/routes/medical-record.route.ts diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index 0023958..305dad6 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -1,74 +1,74 @@ import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; -import { Request, Response, NextFunction } from 'express'; +import { Request, Response } from 'express'; import { RequestWithUser } from '@/interfaces/auth.interface'; -import { promises } from 'dns'; import { MedicalRecordService } from '@/services/medical-records.service'; import { catchAsync } from '@/utils/catchAsync'; export class MedicalRecordController { - constructor(private medicalRecordService: MedicalRecordService) { } + private medicalRecordService = new MedicalRecordService(); - // upload a new medical record public uploadRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { if (!req.file) { res.status(400).json({ message: 'No file uploaded' }); + return; } const recordData: CreateMedicalRecordDto = req.body; const patientId = req.user.id; + const doctorId = req.params.doctorId; const fileBuffer = req.file.buffer; const fileName = req.file.originalname; + const mimeType = req.file.mimetype; - const medical_record = await this.medicalRecordService.createMedicalRecord(patientId, recordData, fileBuffer, fileName); + await this.medicalRecordService.createMedicalRecord( + patientId, + doctorId, + recordData, + fileBuffer, + fileName, + mimeType, + ); res.status(201).json({ - message: 'uploaded MR successfully', - data: medical_record, + message: 'Medical record uploaded successfully', }); }); - // get all MRs for a patient public getPatientMedicalRecords = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; - const records = await this.medicalRecordService.getPatientRecords(patientId); - res.status(201).json({ - message: 'MRs retrieved successfully', - data: records, - }); - }); - - // get all MRs for a doctor - public getDocrotMedicalRecords = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - const doctorId = req.user.id; + const records = await this.medicalRecordService.getPatientFiles(patientId); - const records = await this.medicalRecordService.getDoctorRecords(doctorId); - res.status(201).json({ - message: 'MRs retrieved successfully', + res.status(200).json({ + message: 'Medical records retrieved successfully', data: records, }); }); - public deleteRecord = catchAsync(async (req: Request, res: Response, next: NextFunction): Promise => { - const record_id = req.params.id; + public getRecordFile = catchAsync(async (req: Request, res: Response): Promise => { + const recordId = req.params.id; - await this.medicalRecordService.deleteRecord(record_id); + const { buffer, ...metadata } = await this.medicalRecordService.getRecordFile(recordId); res.status(200).json({ - message: 'deleted MR successfully', + message: 'Medical record retrieved successfully', + data: { + ...metadata, + file: buffer.toString('base64'), + }, }); }); -} - - - - - - - + public deleteRecord = catchAsync(async (req: Request, res: Response): Promise => { + const recordId = req.params.id; + await this.medicalRecordService.deleteRecord(recordId); + res.status(200).json({ + message: 'Medical record deleted successfully', + }); + }); +} \ No newline at end of file diff --git a/src/dtos/medicalRecord.dto.ts b/src/dtos/medicalRecord.dto.ts index becd600..a17c58a 100644 --- a/src/dtos/medicalRecord.dto.ts +++ b/src/dtos/medicalRecord.dto.ts @@ -1,4 +1,4 @@ -import { IsEnum, IsNotEmpty, IsDateString, IsOptional, IsUUID, IsNumber, ValidateIf, IsString, Min, IsInt, Max } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional, IsUUID, IsString } from 'class-validator'; import { RecordType } from '@prisma/client'; export class CreateMedicalRecordDto { @@ -18,8 +18,4 @@ export class CreateMedicalRecordDto { @IsUUID() @IsOptional() public appointmentId?: string; -} - -export class UpdateMedicalRecordDto { - -} +} \ No newline at end of file diff --git a/src/interfaces/medicalRecords.interface.ts b/src/interfaces/medicalRecords.interface.ts index 3ec93df..8b57ad6 100644 --- a/src/interfaces/medicalRecords.interface.ts +++ b/src/interfaces/medicalRecords.interface.ts @@ -1,15 +1,17 @@ -import { User } from './users.interface'; import { RecordType } from '@prisma/client'; export interface MedicalRecord { - id: string; - patient_id: string; - clinic_id: string; - doctor_id?: string; - appointment_id?: string; - name: string; - cid: string; - type: RecordType; - mime_type: string; + id: string; + patient_id: string; + clinic_id: string; + doctor_id?: string; + appointment_id?: string; + name: string; + cid: string; + type: RecordType; + mime_type: string; } +export interface MedicalRecordFile extends MedicalRecord { + buffer: Buffer; +} diff --git a/src/middlewares/upload.middleware.ts b/src/middlewares/upload.middleware.ts index bf5c202..c8b2950 100644 --- a/src/middlewares/upload.middleware.ts +++ b/src/middlewares/upload.middleware.ts @@ -13,6 +13,7 @@ const AllowedFileTypes = [ 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', + 'application/json', ]; diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts new file mode 100644 index 0000000..8e8a52e --- /dev/null +++ b/src/routes/medical-record.route.ts @@ -0,0 +1,224 @@ +import { Routes } from "@/interfaces"; +import { ValidationMiddleware } from "@/middlewares/validation.middleware"; +import { Router } from "express"; +import { MedicalRecordController } from "@/controllers/medical-records.controller"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; +import { Role } from "@prisma/client"; +import { CreateMedicalRecordDto } from "@/dtos/medical-records.dto"; +import { uploadSingleFile } from "@/middlewares/upload.middleware"; + +export class MedicalRecordRoute implements Routes { + public path = '/records'; + public router = Router(); + public medicalRecordController = new MedicalRecordController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + + this.router.get( + `${this.path}/:id`, + /* + #swagger.path = '/records/{id}' + #swagger.method = 'get' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Downloads and decrypts a single medical record file. Returns raw file bytes with appropriate Content-Type header.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['id'] = { + in: 'path', + description: 'UUID of the medical record', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Decrypted file bytes streamed back with Content-Type, Content-Disposition, x-record-id, x-patient-id, x-record-type headers set' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Record not found or already deleted' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT, Role.DOCTOR), + this.medicalRecordController.getRecordFile + ); + + this.router.post( + `${this.path}/:doctorId/upload`, + /* + #swagger.path = '/records/{doctorId}/upload' + #swagger.method = 'post' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Patient uploads a new medical record file' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['doctorId'] = { + in: 'path', + description: 'UUID of the doctor associated with this record', + required: true, + type: 'string' + } + + #swagger.parameters['file'] = { + in: 'formData', + description: 'The medical record file', + required: true, + type: 'file' + } + + #swagger.parameters['name'] = { + in: 'formData', + description: 'Display name for the record', + required: true, + type: 'string' + } + + #swagger.parameters['type'] = { + in: 'formData', + description: 'Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)', + required: true, + type: 'string' + } + + #swagger.parameters['clinicId'] = { + in: 'formData', + description: 'UUID of the clinic', + required: true, + type: 'string' + } + + #swagger.parameters['appointmentId'] = { + in: 'formData', + description: 'UUID of the appointment (optional)', + required: false, + type: 'string' + } + + #swagger.responses[201] = { + description: 'Medical record uploaded successfully', + schema: { + message: 'Medical record uploaded successfully' + } + } + #swagger.responses[400] = { + description: 'No file uploaded or validation failed' + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Patient encryption key not found' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + uploadSingleFile, + ValidationMiddleware(CreateMedicalRecordDto), + this.medicalRecordController.uploadRecord + ); + + + this.router.get( + `${this.path}/patient`, + /* + #swagger.path = '/records/patient' + #swagger.method = 'get' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Retrieves all medical record metadata for the authenticated patient (no file bytes)' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Medical records retrieved successfully', + schema: { + message: 'Medical records retrieved successfully', + data: [ + { + id: 'uuid-string', + patient_id: 'uuid-string', + clinic_id: 'uuid-string', + doctor_id: 'uuid-string', + appointment_id: 'uuid-string', + name: 'Blood Test Results', + type: 'LAB_RESULT', + mime_type: 'application/pdf', + cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' + } + ] + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.getPatientMedicalRecords + ); + + + + this.router.delete( + `${this.path}/:id`, + /* + #swagger.path = '/records/{id}' + #swagger.method = 'delete' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Soft-deletes a medical record (sets deleted_at). File remains on IPFS but is inaccessible via the API.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['id'] = { + in: 'path', + description: 'UUID of the medical record to delete', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Medical record deleted successfully', + schema: { + message: 'Medical record deleted successfully' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[404] = { + description: 'Record not found or already deleted' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT, Role.DOCTOR), + this.medicalRecordController.deleteRecord + ); + } +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 7f7fda8..48d8856 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,13 +10,14 @@ import { AppointmentRoute } from './routes/appointment.route'; import { QueueRoute } from './routes/queue.route'; import { NurseRoute } from './routes/nurse.route'; import { UsersRoute } from './routes/user.route'; +import { MedicalRecordRoute } from './routes/medical-record.route'; ValidateEnv(); const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute(), new QueueRoute(), - new UsersRoute(), new NurseRoute(), + new UsersRoute(), new NurseRoute(),new MedicalRecordRoute() ]); app.listen(); diff --git a/src/services/encryption.service.ts b/src/services/encryption.service.ts index 1b6d4fb..bce6add 100644 --- a/src/services/encryption.service.ts +++ b/src/services/encryption.service.ts @@ -16,8 +16,8 @@ export class EncryptionService { const iv = crypto.randomBytes(this.ivLength); const cipher = crypto.createCipheriv(this.algorithm, dek, iv); - const tag = cipher.getAuthTag(); const encryptedData = Buffer.concat([cipher.update(fileBuffer), cipher.final()]); + const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, encryptedData]) } @@ -39,9 +39,9 @@ export class EncryptionService { const iv = crypto.randomBytes(this.ivLength); const cipher = crypto.createCipheriv(this.algorithm, masterKey, iv); - const tag = cipher.getAuthTag(); const encryptedData = Buffer.concat([cipher.update(dek), cipher.final()]); + const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, encryptedData]).toString('hex'); } diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index 43cb70e..e32e91a 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -1,6 +1,7 @@ import { PinataSDK } from 'pinata'; import { HttpException } from '@/exceptions/HttpException'; import { Service } from 'typedi'; +import { Blob, File } from 'buffer'; @Service() export class IpfsService { @@ -28,7 +29,7 @@ export class IpfsService { public async getFile(cid: string): Promise { try { // CID → gateway URL → HTTP request → raw bytes stream → read all bytes → Buffer - const url = `https://${process.env.PINATA_GATEWAY}/ipfs/${cid}`; + const url = `https://${process.env.PINATA_GATEWAY}/files/${cid}?pinataGatewayToken=${process.env.PINATA_GATEWAY_TOKEN}`; const response = await fetch(url); if (!response.ok) { diff --git a/src/services/key-management.service.ts b/src/services/key-management.service.ts index 51f81e6..a00bfe1 100644 --- a/src/services/key-management.service.ts +++ b/src/services/key-management.service.ts @@ -41,11 +41,10 @@ export class KeyManagementService { }); if (!keyRecord) { - const error = createBilingualError(404, ErrorMessages.PATIENT_KEY_NOT_FOUND); - throw new HttpException(error.status, error.message, error.messageAr); - + await this.createPatientKey(patientId); + // const error = createBilingualError(404, ErrorMessages.PATIENT_KEY_NOT_FOUND); + // throw new HttpException(error.status, error.message, error.messageAr); } - return this.encryptionService.decryptDEK(keyRecord.encrypted_key); } diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index a458ff5..b844a27 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -1,5 +1,6 @@ import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { MedicalRecord, MedicalRecordFile } from '@/interfaces/medicalRecords.interface'; import prisma from '@/config/prisma'; import { Service } from 'typedi'; import { IpfsService } from '@/services/ipfs.service'; @@ -15,8 +16,6 @@ export class MedicalRecordService { private encryptionService = new EncryptionService(); private keyManagementService = new KeyManagementService(); - - // create a new MR public async createMedicalRecord( patientId: string, doctorId: string, @@ -25,11 +24,15 @@ export class MedicalRecordService { fileName: string, mimeType: string, ): Promise { + console.log("we r in"); const patientDEK = await this.keyManagementService.getPatientDEK(patientId); + console.log(`patient key ${patientDEK}`); const encryptedFile = this.encryptionService.encryptFile(fileBuffer, patientDEK); + console.log(`encryptedFile ${encryptedFile}`) patientDEK.fill(0); const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + console.log(`got cid ${cid}`); const keyRecord = await prisma.encryptionKey.findUnique({ where: { @@ -40,7 +43,6 @@ export class MedicalRecordService { }, }); - // save to db await prisma.medicalRecord.create({ data: { patient_id: patientId, @@ -57,11 +59,23 @@ export class MedicalRecordService { } - public async getRecordFile(recordId: string): Promise<{ buffer: Buffer; mimeType: string; name: string }> { + public async getRecordFile(recordId: string): Promise { + console.log("inside the service"); const record = await prisma.medicalRecord.findFirst({ where: { id: recordId, deleted_at: null + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, } }); @@ -69,20 +83,65 @@ export class MedicalRecordService { const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } + console.log("we got heree"); const encryptedFile = await this.ipfsService.getFile(record.cid); + console.log(`encryptedFile ${encryptedFile}`) const patientDEK = await this.keyManagementService.getPatientDEK(record.patient_id); const decryptedFile = this.encryptionService.decryptFile(encryptedFile, patientDEK); + console.log(`decryptedFile ${decryptedFile}`) patientDEK.fill(0); return { - buffer: decryptedFile, - mimeType: record.mime_type, + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, name: record.name, + type: record.type, + mime_type: record.mime_type, + cid: record.cid, + buffer: decryptedFile, }; } + public async getPatientFiles(patientId: string): Promise { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + deleted_at: null + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, + }, + orderBy: { + created_at: 'desc' + }, + }); + + return records.map(record => ({ + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, + name: record.name, + type: record.type, + cid: record.cid, + mime_type: record.mime_type, + })); + } + // delete any MR (soft) public async deleteRecord(recordId: string): Promise { const record = await prisma.medicalRecord.findFirst({ From 807c737934a1ea22bb039b519bd3cfa223f25790 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 4 Mar 2026 20:12:39 +0200 Subject: [PATCH 178/210] fix: get files (ipfs) --- src/controllers/medical-records.controller.ts | 17 +++++++++++++++ src/routes/medical-record.route.ts | 11 +++++----- src/services/ipfs.service.ts | 21 ++++++++----------- src/services/medical-records.service.ts | 6 ++---- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index 305dad6..7a66ac0 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -71,4 +71,21 @@ export class MedicalRecordController { message: 'Medical record deleted successfully', }); }); + + // // metadata only + // public getRecordMetadata = catchAsync(async (req: Request, res: Response): Promise => { + // const record = await this.medicalRecordService.getRecordMetadata(req.params.id); + // res.status(200).json({ + // message: 'Medical record retrieved successfully', + // data: record, + // }); + // }); + + // // raw file stream + // public getRecordFile = catchAsync(async (req: Request, res: Response): Promise => { + // const record = await this.medicalRecordService.getRecordFile(req.params.id); + // res.setHeader('Content-Type', record.mime_type); + // res.setHeader('Content-Disposition', `inline; filename="${record.name}"`); + // res.status(200).send(record.buffer); + // }); } \ No newline at end of file diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 8e8a52e..3aaea65 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -8,7 +8,7 @@ import { CreateMedicalRecordDto } from "@/dtos/medical-records.dto"; import { uploadSingleFile } from "@/middlewares/upload.middleware"; export class MedicalRecordRoute implements Routes { - public path = '/records'; + public path = '/record'; public router = Router(); public medicalRecordController = new MedicalRecordController(); @@ -21,7 +21,7 @@ export class MedicalRecordRoute implements Routes { this.router.get( `${this.path}/:id`, /* - #swagger.path = '/records/{id}' + #swagger.path = '/record/{id}' #swagger.method = 'get' #swagger.tags = ['Medical Records'] #swagger.description = 'Downloads and decrypts a single medical record file. Returns raw file bytes with appropriate Content-Type header.' @@ -51,14 +51,13 @@ export class MedicalRecordRoute implements Routes { } */ AuthMiddleware, - RoleMiddleware(Role.PATIENT, Role.DOCTOR), this.medicalRecordController.getRecordFile ); this.router.post( `${this.path}/:doctorId/upload`, /* - #swagger.path = '/records/{doctorId}/upload' + #swagger.path = '/record/{doctorId}/upload' #swagger.method = 'post' #swagger.tags = ['Medical Records'] #swagger.description = 'Patient uploads a new medical record file' @@ -139,7 +138,7 @@ export class MedicalRecordRoute implements Routes { this.router.get( `${this.path}/patient`, /* - #swagger.path = '/records/patient' + #swagger.path = '/record/patient' #swagger.method = 'get' #swagger.tags = ['Medical Records'] #swagger.description = 'Retrieves all medical record metadata for the authenticated patient (no file bytes)' @@ -184,7 +183,7 @@ export class MedicalRecordRoute implements Routes { this.router.delete( `${this.path}/:id`, /* - #swagger.path = '/records/{id}' + #swagger.path = '/record/{id}' #swagger.method = 'delete' #swagger.tags = ['Medical Records'] #swagger.description = 'Soft-deletes a medical record (sets deleted_at). File remains on IPFS but is inaccessible via the API.' diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index e32e91a..638b627 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -19,26 +19,23 @@ export class IpfsService { const file = new File([fileData], fileName, { type: mimeType }); const upload = await this.pinata.upload.file(file); return upload.cid; - } + } catch (e) { throw new HttpException(500, `IPFS upload failed: ${e.message}`); } } - public async getFile(cid: string): Promise { try { - // CID → gateway URL → HTTP request → raw bytes stream → read all bytes → Buffer - const url = `https://${process.env.PINATA_GATEWAY}/files/${cid}?pinataGatewayToken=${process.env.PINATA_GATEWAY_TOKEN}`; - const response = await fetch(url); + const response = await this.pinata.gateways.get(cid); - if (!response.ok) { - throw new Error(`Gateway responded with ${response.status}`); + if (response.data instanceof Blob) { + const arrayBuffer = await response.data.arrayBuffer(); + return Buffer.from(arrayBuffer); } - - const arrayBuffer = await response.arrayBuffer(); - return Buffer.from(arrayBuffer); - } catch (e) { + return Buffer.from(response.data as string, 'binary'); + } + catch (e) { throw new HttpException(500, `IPFS fetch failed: ${e.message}`); } } @@ -46,7 +43,7 @@ export class IpfsService { public async deleteFile(cid: string): Promise { try { await this.pinata.files.delete([cid]); - } + } catch (e) { throw new HttpException(500, `IPFS delete failed: ${e.message}`); } diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index b844a27..50f35a8 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -60,7 +60,6 @@ export class MedicalRecordService { } public async getRecordFile(recordId: string): Promise { - console.log("inside the service"); const record = await prisma.medicalRecord.findFirst({ where: { id: recordId, @@ -83,14 +82,13 @@ export class MedicalRecordService { const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); throw new HttpException(error.status, error.message, error.messageAr); } - console.log("we got heree"); const encryptedFile = await this.ipfsService.getFile(record.cid); - console.log(`encryptedFile ${encryptedFile}`) + console.log(`encryptedFile`) const patientDEK = await this.keyManagementService.getPatientDEK(record.patient_id); const decryptedFile = this.encryptionService.decryptFile(encryptedFile, patientDEK); - console.log(`decryptedFile ${decryptedFile}`) + console.log(`decryptedFile`) patientDEK.fill(0); return { From a741b5ede2ba9037056271ea9cb85255bb509847 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Wed, 4 Mar 2026 20:23:30 +0200 Subject: [PATCH 179/210] update get patient files route --- src/routes/medical-record.route.ts | 87 ++++++++++++------------- src/services/medical-records.service.ts | 6 -- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 3aaea65..172dce3 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -18,6 +18,49 @@ export class MedicalRecordRoute implements Routes { private initializeRoutes() { + this.router.get( + `${this.path}/patient`, + /* + #swagger.path = '/record/patient' + #swagger.method = 'get' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Retrieves all medical record metadata for the authenticated patient (no file bytes)' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Medical records retrieved successfully', + schema: { + message: 'Medical records retrieved successfully', + data: [ + { + id: 'uuid-string', + patient_id: 'uuid-string', + clinic_id: 'uuid-string', + doctor_id: 'uuid-string', + appointment_id: 'uuid-string', + name: 'Blood Test Results', + type: 'LAB_RESULT', + mime_type: 'application/pdf', + cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' + } + ] + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.getPatientMedicalRecords + ); + this.router.get( `${this.path}/:id`, /* @@ -133,50 +176,6 @@ export class MedicalRecordRoute implements Routes { ValidationMiddleware(CreateMedicalRecordDto), this.medicalRecordController.uploadRecord ); - - - this.router.get( - `${this.path}/patient`, - /* - #swagger.path = '/record/patient' - #swagger.method = 'get' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Retrieves all medical record metadata for the authenticated patient (no file bytes)' - - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - - #swagger.responses[200] = { - description: 'Medical records retrieved successfully', - schema: { - message: 'Medical records retrieved successfully', - data: [ - { - id: 'uuid-string', - patient_id: 'uuid-string', - clinic_id: 'uuid-string', - doctor_id: 'uuid-string', - appointment_id: 'uuid-string', - name: 'Blood Test Results', - type: 'LAB_RESULT', - mime_type: 'application/pdf', - cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' - } - ] - } - } - #swagger.responses[401] = { - description: 'Unauthorized – missing or invalid token' - } - */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT), - this.medicalRecordController.getPatientMedicalRecords - ); diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 50f35a8..d7d3d93 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -24,15 +24,11 @@ export class MedicalRecordService { fileName: string, mimeType: string, ): Promise { - console.log("we r in"); const patientDEK = await this.keyManagementService.getPatientDEK(patientId); - console.log(`patient key ${patientDEK}`); const encryptedFile = this.encryptionService.encryptFile(fileBuffer, patientDEK); - console.log(`encryptedFile ${encryptedFile}`) patientDEK.fill(0); const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); - console.log(`got cid ${cid}`); const keyRecord = await prisma.encryptionKey.findUnique({ where: { @@ -84,11 +80,9 @@ export class MedicalRecordService { } const encryptedFile = await this.ipfsService.getFile(record.cid); - console.log(`encryptedFile`) const patientDEK = await this.keyManagementService.getPatientDEK(record.patient_id); const decryptedFile = this.encryptionService.decryptFile(encryptedFile, patientDEK); - console.log(`decryptedFile`) patientDEK.fill(0); return { From 2b38d52a41a8fc44020f5a67517e844f73291023 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Mar 2026 06:37:20 +0200 Subject: [PATCH 180/210] initialized backblaze and implemented get uploadUrl api --- package-lock.json | 2120 ++++++++++++++--- package.json | 2 + src/app.ts | 2 +- src/config/index.ts | 5 +- src/config/storage.ts | 15 + src/controllers/ai_appointments.controller.ts | 32 + src/controllers/appointment.controller.ts | 6 +- src/interfaces/enums.interface.ts | 7 + src/routes/ai_appointments.route.ts | 22 + src/routes/appointment.route.ts | 6 +- src/services/ai_appointments.service.ts | 32 + src/services/appointment.service.ts | 5 +- src/test/backblaze.test.js | 72 + src/utils/responseMessages.ts | 6 + tsconfig.json | 2 +- 15 files changed, 1990 insertions(+), 344 deletions(-) create mode 100644 src/config/storage.ts create mode 100644 src/controllers/ai_appointments.controller.ts create mode 100644 src/routes/ai_appointments.route.ts create mode 100644 src/services/ai_appointments.service.ts create mode 100644 src/test/backblaze.test.js diff --git a/package-lock.json b/package-lock.json index 187369f..dc4d7fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.1002.0", + "@aws-sdk/s3-request-presigner": "^3.1002.0", "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "6.18.0", @@ -125,11 +127,87 @@ "openapi-types": ">=7" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -144,7 +222,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, "dependencies": { "tslib": "^2.6.2" }, @@ -156,7 +233,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" @@ -169,7 +245,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" @@ -182,7 +257,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -196,7 +270,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, "dependencies": { "tslib": "^2.6.2" } @@ -205,7 +278,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", @@ -216,7 +288,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, "dependencies": { "tslib": "^2.6.2" }, @@ -228,26 +299,560 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1002.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1002.0.tgz", + "integrity": "sha512-tc+vZgvjcm+1Ot+YhQjXZxVELKGGGO3D5cuR4p5xaeitXYX2+RRiz4/WdSak9slumIClnlXsdqhJ0OHognUT+w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/credential-provider-node": "^3.972.16", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.6", + "@aws-sdk/middleware-expect-continue": "^3.972.6", + "@aws-sdk/middleware-flexible-checksums": "^3.973.3", + "@aws-sdk/middleware-host-header": "^3.972.6", + "@aws-sdk/middleware-location-constraint": "^3.972.6", + "@aws-sdk/middleware-logger": "^3.972.6", + "@aws-sdk/middleware-recursion-detection": "^3.972.6", + "@aws-sdk/middleware-sdk-s3": "^3.972.17", + "@aws-sdk/middleware-ssec": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.17", + "@aws-sdk/region-config-resolver": "^3.972.6", + "@aws-sdk/signature-v4-multi-region": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@aws-sdk/util-user-agent-browser": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.973.2", + "@smithy/config-resolver": "^4.4.9", + "@smithy/core": "^3.23.7", + "@smithy/eventstream-serde-browser": "^4.2.10", + "@smithy/eventstream-serde-config-resolver": "^4.3.10", + "@smithy/eventstream-serde-node": "^4.2.10", + "@smithy/fetch-http-handler": "^5.3.12", + "@smithy/hash-blob-browser": "^4.2.11", + "@smithy/hash-node": "^4.2.10", + "@smithy/hash-stream-node": "^4.2.10", + "@smithy/invalid-dependency": "^4.2.10", + "@smithy/md5-js": "^4.2.10", + "@smithy/middleware-content-length": "^4.2.10", + "@smithy/middleware-endpoint": "^4.4.21", + "@smithy/middleware-retry": "^4.4.38", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/node-http-handler": "^4.4.13", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-body-length-node": "^4.2.2", + "@smithy/util-defaults-mode-browser": "^4.3.37", + "@smithy/util-defaults-mode-node": "^4.2.40", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/util-stream": "^4.5.16", + "@smithy/util-utf8": "^4.2.1", + "@smithy/util-waiter": "^4.2.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.17.tgz", + "integrity": "sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.9", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.15.tgz", + "integrity": "sha512-RhHQG1lhkWHL4tK1C/KDjaOeis+9U0tAMnWDiwiSVQZMC7CsST9Xin+sK89XywJ5g/tyABtb7TvFePJ4Te5XSQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.17.tgz", + "integrity": "sha512-b/bDL76p51+yQ+0O9ZDH5nw/ioE0sRYkjwjOwFWAWZXo6it2kQZUOXhVpjohx3ldKyUxt/SwAivjUu1Nr/PWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/fetch-http-handler": "^5.3.12", + "@smithy/node-http-handler": "^4.4.13", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.16", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.15.tgz", + "integrity": "sha512-qWnM+wB8MmU2kKY7f4KowKjOjkwRosaFxrtseEEIefwoXn1SjN+CbHzXBVdTAQxxkbBiqhPgJ/WHiPtES4grRQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/credential-provider-env": "^3.972.15", + "@aws-sdk/credential-provider-http": "^3.972.17", + "@aws-sdk/credential-provider-login": "^3.972.15", + "@aws-sdk/credential-provider-process": "^3.972.15", + "@aws-sdk/credential-provider-sso": "^3.972.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.15", + "@aws-sdk/nested-clients": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.16.tgz", + "integrity": "sha512-7mlt14Ee4rPFAFUVgpWE7+0CBhetJJyzVFqfIsMp7sgyOSm9Y/+qHZOWAuK5I4JNc+Y5PltvJ9kssTzRo92iXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.15", + "@aws-sdk/credential-provider-http": "^3.972.17", + "@aws-sdk/credential-provider-ini": "^3.972.15", + "@aws-sdk/credential-provider-process": "^3.972.15", + "@aws-sdk/credential-provider-sso": "^3.972.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.15.tgz", + "integrity": "sha512-PrH3iTeD18y/8uJvQD2s/T87BTGhsdS/1KZU7ReWHXsplBwvCqi7AbnnNbML1pFlQwRWCE2RdSZFWDVId3CvkA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.15.tgz", + "integrity": "sha512-M/+LBHTPKZxxXckM6m4dnJeR+jlm9NynH9b2YDswN4Zj2St05SK/crdL3Wy3WfJTZootnnhm3oTh87Usl7PS7w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/nested-clients": "^3.996.5", + "@aws-sdk/token-providers": "3.1002.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.15.tgz", + "integrity": "sha512-QTH6k93v+UOfFam/ado8zc71tH+enTVyuvLy9uEWXX1x894dN5ovtf/MdBDgFwq3g6c9mbtgVJ4B+yBqDtXvdA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/nested-clients": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", + "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", + "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", + "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.17.tgz", + "integrity": "sha512-uSyOGoVFMP44pTt29MIMfsOjegqE/7lT0K3HG0GWPiH2lD4rqZC/TRi/kH4zrGiOQdsaLc+dkfd7Sb2q8vh+gA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.16", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.17.tgz", + "integrity": "sha512-HHArkgWzomuwufXwheQqkddu763PWCpoNTq1dGjqXzJT/lojX3VlOqjNSR2Xvb6/T9ISfwYcMOcbFgUp4EWxXA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@smithy/core": "^3.23.7", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.5.tgz", + "integrity": "sha512-zn0WApcULn7Rtl6T+KP2CQTZo/7wOa2YV1yHQnbijTQoi4YXQHM8s21JcJzt33/mqPh8AdvWX1f+83KvKuxlZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/middleware-host-header": "^3.972.6", + "@aws-sdk/middleware-logger": "^3.972.6", + "@aws-sdk/middleware-recursion-detection": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.17", + "@aws-sdk/region-config-resolver": "^3.972.6", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@aws-sdk/util-user-agent-browser": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.973.2", + "@smithy/config-resolver": "^4.4.9", + "@smithy/core": "^3.23.7", + "@smithy/fetch-http-handler": "^5.3.12", + "@smithy/hash-node": "^4.2.10", + "@smithy/invalid-dependency": "^4.2.10", + "@smithy/middleware-content-length": "^4.2.10", + "@smithy/middleware-endpoint": "^4.4.21", + "@smithy/middleware-retry": "^4.4.38", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/node-http-handler": "^4.4.13", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-body-length-node": "^4.2.2", + "@smithy/util-defaults-mode-browser": "^4.3.37", + "@smithy/util-defaults-mode-node": "^4.2.40", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", + "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/config-resolver": "^4.4.9", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.5.tgz", + "integrity": "sha512-AVIhf74wRMzU1WBPVzcGPjlADF5VxZ8m8Ctm1v7eO4/reWMhZnEBn4tlR4vM4pOYFkdrYp3MTzYVZIikCO+53Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.1002.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1002.0.tgz", + "integrity": "sha512-x972uKOydFn4Rb0PZJzLdNW59rH0KWC78Q2JbQzZpGlGt0DxjYdDRwBG6F42B1MyaEwHGqO/tkGc4r3/PRFfMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/nested-clients": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", + "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.2.tgz", + "integrity": "sha512-lpaIuekdkpw7VRiik0IZmd6TyvEUcuLgKZ5fKRGpCA3I4PjrD/XH15sSwW+OptxQjNU4DEzSxag70spC9SluvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.9.tgz", + "integrity": "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, + "node_modules/@aws-sdk/client-s3/node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" }, - "engines": { - "node": ">=14.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, "node_modules/@aws-sdk/client-sesv2": { @@ -374,6 +979,19 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz", + "integrity": "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.922.0.tgz", @@ -435,6 +1053,284 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.15.tgz", + "integrity": "sha512-x92FJy34/95wgu+qOGD8SHcgh1hZ9Qx2uFtQEGn4m9Ljou8ICIv3Ybq5yxdB7A60S8ZGCQB0mIopmjJwiLbh5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/nested-clients": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.17.tgz", + "integrity": "sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.9", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", + "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", + "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", + "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.17.tgz", + "integrity": "sha512-HHArkgWzomuwufXwheQqkddu763PWCpoNTq1dGjqXzJT/lojX3VlOqjNSR2Xvb6/T9ISfwYcMOcbFgUp4EWxXA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@smithy/core": "^3.23.7", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.5.tgz", + "integrity": "sha512-zn0WApcULn7Rtl6T+KP2CQTZo/7wOa2YV1yHQnbijTQoi4YXQHM8s21JcJzt33/mqPh8AdvWX1f+83KvKuxlZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/middleware-host-header": "^3.972.6", + "@aws-sdk/middleware-logger": "^3.972.6", + "@aws-sdk/middleware-recursion-detection": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.17", + "@aws-sdk/region-config-resolver": "^3.972.6", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@aws-sdk/util-user-agent-browser": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.973.2", + "@smithy/config-resolver": "^4.4.9", + "@smithy/core": "^3.23.7", + "@smithy/fetch-http-handler": "^5.3.12", + "@smithy/hash-node": "^4.2.10", + "@smithy/invalid-dependency": "^4.2.10", + "@smithy/middleware-content-length": "^4.2.10", + "@smithy/middleware-endpoint": "^4.4.21", + "@smithy/middleware-retry": "^4.4.38", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/node-http-handler": "^4.4.13", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-body-length-node": "^4.2.2", + "@smithy/util-defaults-mode-browser": "^4.3.37", + "@smithy/util-defaults-mode-node": "^4.2.40", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", + "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/config-resolver": "^4.4.9", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", + "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.2.tgz", + "integrity": "sha512-lpaIuekdkpw7VRiik0IZmd6TyvEUcuLgKZ5fKRGpCA3I4PjrD/XH15sSwW+OptxQjNU4DEzSxag70spC9SluvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.9.tgz", + "integrity": "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.922.0.tgz", @@ -455,61 +1351,227 @@ "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.922.0.tgz", + "integrity": "sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.922.0.tgz", + "integrity": "sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==", + "dev": true, + "dependencies": { + "@aws-sdk/client-sso": "3.922.0", + "@aws-sdk/core": "3.922.0", + "@aws-sdk/token-providers": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.922.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.922.0.tgz", + "integrity": "sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.922.0", + "@aws-sdk/nested-clients": "3.922.0", + "@aws-sdk/types": "3.922.0", + "@smithy/property-provider": "^4.2.4", + "@smithy/shared-ini-file-loader": "^4.3.4", + "@smithy/types": "^4.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.6.tgz", + "integrity": "sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.6.tgz", + "integrity": "sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.922.0.tgz", - "integrity": "sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==", - "dev": true, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.973.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.3.tgz", + "integrity": "sha512-C9Mu9pXMpQh7jBydx0MrfQxNIKwJvKbVbJJ0GZthM+cQ+KTChXA01MwttRsMq0ZRb4pBJZQtIKDUxXusDr5OKg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/crc64-nvme": "^3.972.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.16", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.922.0.tgz", - "integrity": "sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==", - "dev": true, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.17.tgz", + "integrity": "sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.922.0", - "@aws-sdk/core": "3.922.0", - "@aws-sdk/token-providers": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.9", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.922.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.922.0.tgz", - "integrity": "sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==", - "dev": true, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.922.0", - "@aws-sdk/nested-clients": "3.922.0", - "@aws-sdk/types": "3.922.0", - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.9.tgz", + "integrity": "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, "node_modules/@aws-sdk/middleware-host-header": { @@ -527,6 +1589,33 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.6.tgz", + "integrity": "sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.922.0.tgz", @@ -582,6 +1671,33 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.6.tgz", + "integrity": "sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.922.0.tgz", @@ -665,6 +1781,149 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.1002.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1002.0.tgz", + "integrity": "sha512-vzbygdP2KMRoD7jheRNBlYVvrmGrwyeec+6KwHiM9AtFQ+tx4EvF8x0Wo+7FjVn1PL3t5Do7i54f4ozKCYJleQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "^3.996.5", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-format-url": "^3.972.6", + "@smithy/middleware-endpoint": "^4.4.21", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/core": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.17.tgz", + "integrity": "sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.9", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.17.tgz", + "integrity": "sha512-uSyOGoVFMP44pTt29MIMfsOjegqE/7lT0K3HG0GWPiH2lD4rqZC/TRi/kH4zrGiOQdsaLc+dkfd7Sb2q8vh+gA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.17", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.23.7", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.1", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.16", + "@smithy/util-utf8": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.5.tgz", + "integrity": "sha512-AVIhf74wRMzU1WBPVzcGPjlADF5VxZ8m8Ctm1v7eO4/reWMhZnEBn4tlR4vM4pOYFkdrYp3MTzYVZIikCO+53Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.17", + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.9.tgz", + "integrity": "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.922.0.tgz", @@ -704,7 +1963,6 @@ "version": "3.922.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.922.0.tgz", "integrity": "sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==", - "dev": true, "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" @@ -741,11 +1999,38 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.6.tgz", + "integrity": "sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.4", + "@smithy/querystring-builder": "^4.2.10", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.893.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", - "dev": true, "dependencies": { "tslib": "^2.6.2" }, @@ -3040,91 +4325,201 @@ "type-detect": "4.0.8" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.11.tgz", + "integrity": "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.10.tgz", + "integrity": "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.8.tgz", + "integrity": "sha512-f7uPeBi7ehmLT4YF2u9j3qx6lSnurG1DLXOsTtJrIRNDF7VXio4BGHQ+SQteN/BrUVudbkuL4v7oOsRCzq4BqA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.12", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz", + "integrity": "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.11.tgz", + "integrity": "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.11.tgz", + "integrity": "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA==", + "license": "Apache-2.0", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@smithy/eventstream-serde-universal": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.4.tgz", - "integrity": "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==", - "dev": true, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.11.tgz", + "integrity": "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.1.tgz", - "integrity": "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ==", - "dev": true, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.11.tgz", + "integrity": "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.4", - "@smithy/util-middleware": "^4.2.4", + "@smithy/eventstream-serde-universal": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/core": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.2.tgz", - "integrity": "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==", - "dev": true, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.11.tgz", + "integrity": "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-stream": "^4.5.5", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@smithy/eventstream-codec": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.4.tgz", - "integrity": "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==", - "dev": true, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz", + "integrity": "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.5.tgz", - "integrity": "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==", - "dev": true, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.12.tgz", + "integrity": "sha512-1wQE33DsxkM/waftAhCH9VtJbUGyt1PJ9YRDpOu+q9FUi73LLFUZ2fD8A61g2mT1UY9k7b99+V1xZ41Rz4SHRQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/querystring-builder": "^4.2.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3132,14 +4527,28 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.4.tgz", - "integrity": "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.11.tgz", + "integrity": "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.11.tgz", + "integrity": "sha512-hQsTjwPCRY8w9GK07w1RqJi3e+myh0UaOWBBhZ1UMSDgofH/Q1fEYzU1teaX6HkpX/eWDdm7tAGR0jBPlz9QEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3147,12 +4556,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.4.tgz", - "integrity": "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz", + "integrity": "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3160,11 +4569,25 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.11.tgz", + "integrity": "sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng==", + "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3172,13 +4595,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.4.tgz", - "integrity": "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz", + "integrity": "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3186,18 +4609,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.6.tgz", - "integrity": "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==", - "dev": true, + "version": "4.4.22", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.22.tgz", + "integrity": "sha512-sc81w1o4Jy+/MAQlY3sQ8C7CmSpcvIi3TAzXblUv2hjG11BBSJi/Cw8vDx5BxMxapuH2I+Gc+45vWsgU07WZRQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.17.2", - "@smithy/middleware-serde": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", - "@smithy/url-parser": "^4.2.4", - "@smithy/util-middleware": "^4.2.4", + "@smithy/core": "^3.23.8", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" }, "engines": { @@ -3205,19 +4628,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.6.tgz", - "integrity": "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==", - "dev": true, + "version": "4.4.39", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.39.tgz", + "integrity": "sha512-MCVCxaCzuZgiHtHGV2Ke44nh6t4+8/tO+rTYOzrr2+G4nMLU/qbzNCWKBX54lyEaVcGQrfOJiG2f8imtiw+nIQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/service-error-classification": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-retry": "^4.2.4", - "@smithy/uuid": "^1.1.0", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/service-error-classification": "^4.2.11", + "@smithy/smithy-client": "^4.12.2", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, "engines": { @@ -3225,13 +4648,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.4.tgz", - "integrity": "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==", - "dev": true, + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz", + "integrity": "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3239,12 +4662,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.4.tgz", - "integrity": "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz", + "integrity": "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3252,14 +4675,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.4.tgz", - "integrity": "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==", - "dev": true, + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz", + "integrity": "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.4", - "@smithy/shared-ini-file-loader": "^4.3.4", - "@smithy/types": "^4.8.1", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3267,15 +4690,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.4.tgz", - "integrity": "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==", - "dev": true, + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz", + "integrity": "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/querystring-builder": "^4.2.4", - "@smithy/types": "^4.8.1", + "@smithy/abort-controller": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3283,12 +4706,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.4.tgz", - "integrity": "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.11.tgz", + "integrity": "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3296,12 +4719,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.4.tgz", - "integrity": "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==", - "dev": true, + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.11.tgz", + "integrity": "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3309,13 +4732,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.4.tgz", - "integrity": "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz", + "integrity": "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3323,12 +4746,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.4.tgz", - "integrity": "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz", + "integrity": "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3336,24 +4759,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.4.tgz", - "integrity": "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz", + "integrity": "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1" + "@smithy/types": "^4.13.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.4.tgz", - "integrity": "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==", - "dev": true, + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz", + "integrity": "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3361,18 +4784,18 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.4.tgz", - "integrity": "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==", - "dev": true, + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.11.tgz", + "integrity": "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.4", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3380,17 +4803,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.2.tgz", - "integrity": "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==", - "dev": true, + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.2.tgz", + "integrity": "sha512-HezY3UuG0k4T+4xhFKctLXCA5N2oN+Rtv+mmL8Gt7YmsUY2yhmcLyW75qrSzldfj75IsCW/4UhY3s20KcFnZqA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.17.2", - "@smithy/middleware-endpoint": "^4.3.6", - "@smithy/middleware-stack": "^4.2.4", - "@smithy/protocol-http": "^5.3.4", - "@smithy/types": "^4.8.1", - "@smithy/util-stream": "^4.5.5", + "@smithy/core": "^3.23.8", + "@smithy/middleware-endpoint": "^4.4.22", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" }, "engines": { @@ -3398,10 +4821,10 @@ } }, "node_modules/@smithy/types": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.1.tgz", - "integrity": "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==", - "dev": true, + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", + "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3410,13 +4833,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.4.tgz", - "integrity": "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.11.tgz", + "integrity": "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.4", - "@smithy/types": "^4.8.1", + "@smithy/querystring-parser": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3424,13 +4847,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", - "dev": true, + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3438,10 +4861,10 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3450,10 +4873,10 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3462,12 +4885,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3475,10 +4898,10 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3487,14 +4910,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.5.tgz", - "integrity": "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==", - "dev": true, + "version": "4.3.38", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.38.tgz", + "integrity": "sha512-c8P1mFLNxcsdAMabB8/VUQUbWzFmgujWi4bAXSggcqLYPc8V4U5abqFqOyn+dK4YT+q8UyCVkTO8807t4t2syA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.2", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3502,17 +4925,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.7.tgz", - "integrity": "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw==", - "dev": true, + "version": "4.2.41", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.41.tgz", + "integrity": "sha512-/UG+9MT3UZAR0fLzOtMJMfWGcjjHvgggq924x/CRy8vRbL+yFf3Z6vETlvq8vDH92+31P/1gSOFoo7303wN8WQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.1", - "@smithy/credential-provider-imds": "^4.2.4", - "@smithy/node-config-provider": "^4.3.4", - "@smithy/property-provider": "^4.2.4", - "@smithy/smithy-client": "^4.9.2", - "@smithy/types": "^4.8.1", + "@smithy/config-resolver": "^4.4.10", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.2", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3520,13 +4943,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.4.tgz", - "integrity": "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==", - "dev": true, + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz", + "integrity": "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.4", - "@smithy/types": "^4.8.1", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3534,10 +4957,10 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3546,12 +4969,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.4.tgz", - "integrity": "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.11.tgz", + "integrity": "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3559,13 +4982,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.4.tgz", - "integrity": "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==", - "dev": true, + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.11.tgz", + "integrity": "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.4", - "@smithy/types": "^4.8.1", + "@smithy/service-error-classification": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3573,18 +4996,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.5.tgz", - "integrity": "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==", - "dev": true, + "version": "4.5.17", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.17.tgz", + "integrity": "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.5", - "@smithy/node-http-handler": "^4.4.4", - "@smithy/types": "^4.8.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -3592,10 +5015,10 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -3604,12 +5027,26 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.11.tgz", + "integrity": "sha512-x7Rh2azQPs3XxbvCzcttRErKKvLnbZfqRf/gOjw2pb+ZscX88e5UkRPCB67bVnsFHxayvMvmePfKTqsRb+is1A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", + "@smithy/abort-controller": "^4.2.11", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -3617,10 +5054,10 @@ } }, "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -5127,8 +6564,7 @@ "node_modules/bowser": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", - "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", - "dev": true + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==" }, "node_modules/brace-expansion": { "version": "2.0.2", @@ -7128,6 +8564,18 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/fast-xml-parser": { "version": "5.2.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", @@ -12775,16 +14223,16 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } - ] + ], + "license": "MIT" }, "node_modules/strtok3": { "version": "10.3.4", diff --git a/package.json b/package.json index 727705f..23c2eac 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "schema": "src/prisma/schema.prisma" }, "dependencies": { + "@aws-sdk/client-s3": "^3.1002.0", + "@aws-sdk/s3-request-presigner": "^3.1002.0", "@grpc/grpc-js": "^1.14.0", "@hyperledger/fabric-gateway": "^1.9.0", "@prisma/client": "6.18.0", diff --git a/src/app.ts b/src/app.ts index 33c1f27..32cb5d9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,7 +30,7 @@ export class App { this.env = NODE_ENV || 'development'; this.port = PORT || 3000; this.httpServer = createServer(this.app); - + this.initializeMiddlewares(); this.initializeRoutes(routes); this.initializeErrorHandling(); diff --git a/src/config/index.ts b/src/config/index.ts index e8cb2d9..8b5a3b5 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -7,7 +7,10 @@ export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_ GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GOOGLE_CALLBACK_URL, GMAIL_USER, GMAIL_APP_PASSWORD, FRONTEND_URL, SENDER_EMAIL, - CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } = process.env; + CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET, + Agora_APP_ID, Agora_APP_CERTIFICATE, + B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_ENDPOINT, B2_BUCKET_NAME, B2_REGION_NAME + } = process.env; export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days export const ACCESS_TOKEN_EXPIRY = process.env.ACCESS_TOKEN_EXPIRY || '1h'; // Default 1 hour diff --git a/src/config/storage.ts b/src/config/storage.ts new file mode 100644 index 0000000..2e7689f --- /dev/null +++ b/src/config/storage.ts @@ -0,0 +1,15 @@ +import { B2_ENDPOINT, B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_REGION_NAME } from "."; + +import { S3Client } from "@aws-sdk/client-s3"; + +const s3Client = new S3Client({ + region: B2_REGION_NAME, + endpoint: B2_ENDPOINT, + credentials: { + accessKeyId: B2_APPLICATION_KEY_ID, + secretAccessKey: B2_APPLICATION_KEY, + }, + forcePathStyle: true, +}); + +export default s3Client; \ No newline at end of file diff --git a/src/controllers/ai_appointments.controller.ts b/src/controllers/ai_appointments.controller.ts new file mode 100644 index 0000000..4c8436b --- /dev/null +++ b/src/controllers/ai_appointments.controller.ts @@ -0,0 +1,32 @@ +import { RequestWithUser, USER_ROLE } from "@/interfaces"; +import { AiAppointmentsService } from "@/services/ai_appointments.service"; +import { catchAsync } from "@/utils/catchAsync"; +import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; +import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; +import { Request, Response, NextFunction } from "express"; +import Container from "typedi"; + +export class AiAppointmentsController { + public aiAppointmentsService = Container.get(AiAppointmentsService); + + public getUploadUrl = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const appointmentId = req.params.appointmentId; + const userType = req.query.userType as USER_ROLE.DOCTOR | USER_ROLE.PATIENT; + const objectKey = `appointments/${appointmentId}/${userType}.webm`; + + const isAppointmentExist = await this.aiAppointmentsService.checkAppointmentExistence(appointmentId); + if (!isAppointmentExist) { + const errorMessage = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + return next(errorMessage); + } + const uploadUrl = await this.aiAppointmentsService.getUploadUrl(objectKey); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.UPLOAD_URL_GENERATED); + res.status(200).json({ + ...responseMessage, + data: { + uploadUrl, + objectKey + } + }); + }) +} \ No newline at end of file diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 21543e9..479c415 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -7,6 +7,7 @@ import Container from "typedi"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; import { SocketService } from "@/services/socket.service"; +import { Agora_APP_ID } from "@/config"; export class AppointmentController { @@ -505,7 +506,10 @@ export class AppointmentController { const response = createMultiLangMessage(SuccessResponseMessages.AGORA_TOKEN_GENERATED_SUCCESSFULLY); res.status(200).json({ ...response, - data: token + data: { + token, + appId: Agora_APP_ID + } }); }); } \ No newline at end of file diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index 866427b..e69110f 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -39,4 +39,11 @@ export enum NURSE_FILES { NATIONAL_CARD = 'nationalCard', BONUS_FILE = 'bonusFile', +} + +export enum USER_ROLE { + DOCTOR = 'DOCTOR', + NURSE = 'NURSE', + PATIENT = 'PATIENT', + ADMIN = 'ADMIN', } \ No newline at end of file diff --git a/src/routes/ai_appointments.route.ts b/src/routes/ai_appointments.route.ts new file mode 100644 index 0000000..cc77848 --- /dev/null +++ b/src/routes/ai_appointments.route.ts @@ -0,0 +1,22 @@ +import { AiAppointmentsController } from "@/controllers/ai_appointments.controller"; +import { Routes } from "@/interfaces"; +import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import { Router } from "express"; + +export class AiAppointmentsRoute implements Routes { + public path: string = "" + public router: Router = Router() + private aiAppointmentsController = new AiAppointmentsController(); + + constructor() { + this.initializeRoutes() + }; + + private initializeRoutes(): void { + this.router.get(`${this.path}/:appointmentId/upload-url`, + // AuthMiddleware, + this.aiAppointmentsController.getUploadUrl + ) + + } +} \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index a7b030a..e335ddc 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -6,6 +6,7 @@ import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; import { AuthMiddleware } from "@/middlewares/auth.middleware"; import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto, EditDoctorScheduleDto, HandleDoctorVacationDto } from "@/dtos/appointments.dto"; +import { AiAppointmentsRoute } from "./ai_appointments.route"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -13,13 +14,14 @@ export class AppointmentRoute implements Routes { clinicController = new ClinicController(); doctorController = new DoctorController(); appointmentController = new AppointmentController(); - + private _aiRouter = new AiAppointmentsRoute(); constructor() { this.initializeRoutes(); - // this.router.use() } private initializeRoutes() { + this.router.use(`${this.path}/ai`, this._aiRouter.router); + this.router.get( `${this.path}/doctors`, /* diff --git a/src/services/ai_appointments.service.ts b/src/services/ai_appointments.service.ts new file mode 100644 index 0000000..ff44db5 --- /dev/null +++ b/src/services/ai_appointments.service.ts @@ -0,0 +1,32 @@ +import { B2_BUCKET_NAME } from "@/config"; +import prisma from "@/config/prisma"; +import s3Client from "@/config/storage"; +import { PutObjectCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { Service } from "typedi"; + + +@Service() +export class AiAppointmentsService { + private appointments = prisma.appointment; + + public async checkAppointmentExistence(appointmentId: string): Promise { + const appointment = await this.appointments.findUnique({ + where: { id: appointmentId } + }); + return appointment !== null; + } + + public async getUploadUrl(objectKey: string): Promise { + + const command = new PutObjectCommand({ + Bucket: B2_BUCKET_NAME, + Key: objectKey, + ContentType: 'audio/webm', + }); + + const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); // URL valid for 1 hour + + return uploadUrl; + } +} \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index e4170c1..7736b25 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -9,6 +9,7 @@ import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientA import { QueueService } from './queue.service'; import { start } from 'repl'; import { RtcRole, RtcTokenBuilder } from 'agora-token'; +import { Agora_APP_CERTIFICATE, Agora_APP_ID } from '@/config'; @Service() export class AppointmentService { @@ -1664,8 +1665,8 @@ export class AppointmentService { throw new HttpException(error.status, error.message, error.messageAr); } - const appId = process.env.AGORA_APP_ID; - const appCertificate = process.env.AGORA_APP_CERTIFICATE; + const appId = Agora_APP_ID; + const appCertificate = Agora_APP_CERTIFICATE; if (!appId || !appCertificate) { const error = createBilingualError(500, ErrorMessages.AGORA_CREDENTIALS_NOT_CONFIGURED); diff --git a/src/test/backblaze.test.js b/src/test/backblaze.test.js new file mode 100644 index 0000000..17e8e44 --- /dev/null +++ b/src/test/backblaze.test.js @@ -0,0 +1,72 @@ +// test-upload.js +// Run this with: node test-upload.js + +async function testFrontendUploadFlow() { + // 1. Configuration (Match this to your local server port) + const appointmentId = "dbd33650-7a88-4988-94ec-0c38e4a9ab07"; + const fileType = "DOCTOR"; // Testing the doctor's isolated track + const backendUrl = `http://localhost:3000/appointments/ai/${appointmentId}/upload-url?userType=${fileType}`; + + console.log(`🚀 Step 1: Requesting Presigned URL from Backend...`); + console.log(`GET ${backendUrl}`); + + try { + // --- STEP 1: TALK TO YOUR BACKEND --- + const response = await fetch(backendUrl); + + if (!response.ok) { + throw new Error(`Backend failed with status ${response.status}: ${await response.text()}`); + } + + const data = await response.json(); + + const uploadUrl = data.data.uploadUrl; + const objectKey = data.data.objectKey; + + console.log(uploadUrl); + console.log(objectKey); + console.log(`✅ Success! Backend generated the URL.`); + console.log(`🔑 Object Key: ${objectKey}`); + console.log(`🔗 URL: ${uploadUrl.substring(0, 100)}...\n`); // Truncated for terminal readability + + // --- STEP 2: SIMULATE AUDIO BLOB --- + console.log(`🎙️ Step 2: Creating dummy audio Blob...`); + // We inject fake text data, but tag it as a webm file to satisfy the Content-Type requirement + const fakeAudioData = "This is a dummy string pretending to be binary audio data."; + const dummyBlob = new Blob([fakeAudioData], { type: 'audio/webm' }); + + // --- STEP 3: UPLOAD DIRECTLY TO BACKBLAZE B2 --- + console.log(`☁️ Step 3: Uploading Blob directly to Backblaze...`); + const b2Response = await fetch(uploadUrl, { + method: 'PUT', + headers: { + // This MUST exactly match the ContentType you defined in the PutObjectCommand + 'Content-Type': 'audio/webm' + }, + body: dummyBlob + }); + + if (b2Response.ok) { + console.log(`🎉 SUCCESS! File uploaded directly to B2.`); + console.log(`\n➡️ Next Frontend Step: Call your AI trigger endpoint:`); + console.log(`POST /api/appointments/${appointmentId}/process-cloud-audio`); + console.log(`Body: { "doctorKey": "${objectKey}" }`); + } else { + console.error(`❌ B2 Upload Failed. Status: ${b2Response.status}`); + const errorText = await b2Response.text(); + console.error(errorText); + + // Helpful debugging hints based on common B2/S3 errors + if (b2Response.status === 403) { + console.log("\n💡 Hint: 403 usually means your AWS SDK Signature didn't match. Check your B2_KEY_ID and B2_APPLICATION_KEY."); + console.log("💡 Hint 2: If you get a CORS error in the browser later, remember to apply the CORS rule to your B2 bucket."); + } + } + + } catch (error) { + console.error("\n💥 Test script crashed:", error); + } +} + +// Execute the test +testFrontendUploadFlow(); \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 84c1e8b..07f13be 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -321,6 +321,12 @@ export const SuccessResponseMessages = { message_ar: "تم إنشاء رمز Agora بنجاح.", }, + // BackBlaze B2 success messages + UPLOAD_URL_GENERATED: { + message_en: "BackBlaze B2 upload URL generated successfully.", + message_ar: "تم إنشاء رابط التحميل لـ BackBlaze B2 بنجاح.", + }, + } interface MultiLangMessageObj { diff --git a/tsconfig.json b/tsconfig.json index 700d863..b216a43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,6 @@ "@validators/*": ["validators/*"] } }, - "include": ["src/**/*.ts", "src/**/*.json", ".env"], + "include": ["src/**/*.ts", "src/**/*.json", ".env", "src/test/backblaze.test.js"], "exclude": ["node_modules", "src/http", "src/logs"] } From 3e423e9c9c26c2800a503ec5e115fcbc4b90ab64 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Thu, 5 Mar 2026 08:19:02 +0200 Subject: [PATCH 181/210] made 80% of process and generate SOAP notes using AI --- package-lock.json | 139 ++++++++++- package.json | 1 + src/config/index.ts | 3 +- src/controllers/ai_appointments.controller.ts | 49 +++- src/routes/ai_appointments.route.ts | 4 + src/routes/appointment.route.ts | 12 + src/services/ai_appointments.service.ts | 172 +++++++++++++- src/test/backblaze.test.js | 72 ------ src/test/doctor.webm | Bin 0 -> 232136 bytes src/test/mixed.webm | Bin 0 -> 233102 bytes src/test/mixedAudioAI.test.js | 217 ++++++++++++++++++ src/test/patient.webm | Bin 0 -> 196394 bytes src/test/separateAudioAI.test.js | 96 ++++++++ src/utils/errorMessages.ts | 12 +- src/utils/responseMessages.ts | 6 + tsconfig.json | 2 +- 16 files changed, 695 insertions(+), 90 deletions(-) delete mode 100644 src/test/backblaze.test.js create mode 100644 src/test/doctor.webm create mode 100644 src/test/mixed.webm create mode 100644 src/test/mixedAudioAI.test.js create mode 100644 src/test/patient.webm create mode 100644 src/test/separateAudioAI.test.js diff --git a/package-lock.json b/package-lock.json index dc4d7fb..760a930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "envalid": "^8.1.0", "express": "^5.1.0", "express-session": "^1.18.2", + "groq-sdk": "^0.37.0", "helmet": "^8.1.0", "hpp": "^0.2.3", "ipfs-http-client": "^60.0.1", @@ -5482,6 +5483,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, "node_modules/@types/nodemailer": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.3.tgz", @@ -6014,6 +6025,18 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/abort-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.1.tgz", @@ -6076,6 +6099,18 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/agora-token": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/agora-token/-/agora-token-2.0.5.tgz", @@ -6264,7 +6299,6 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "dev": true, "license": "MIT" }, "node_modules/b4a": { @@ -7188,7 +7222,6 @@ }, "node_modules/combined-stream": { "version": "1.0.8", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -7549,7 +7582,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -7959,7 +7991,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8271,6 +8302,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter2": { "version": "5.0.1", "dev": true, @@ -8823,7 +8863,6 @@ }, "node_modules/form-data": { "version": "4.0.4", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -8846,7 +8885,6 @@ }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8854,7 +8892,6 @@ }, "node_modules/form-data/node_modules/mime-types": { "version": "2.1.35", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8863,6 +8900,19 @@ "node": ">= 0.6" } }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/formidable": { "version": "3.5.4", "dev": true, @@ -9184,6 +9234,42 @@ "dev": true, "license": "MIT" }, + "node_modules/groq-sdk": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.37.0.tgz", + "integrity": "sha512-lT72pcT8b/X5XrzdKf+rWVzUGW1OQSKESmL8fFN5cTbsf02gq6oFam4SVeNtzELt9cYE2Pt3pdGgSImuTbHFDg==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/groq-sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/groq-sdk/node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/groq-sdk/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==", + "license": "MIT" + }, "node_modules/handlebars": { "version": "4.7.8", "dev": true, @@ -9232,7 +9318,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -9388,6 +9473,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "9.1.7", "dev": true, @@ -11812,6 +11906,26 @@ "node": ">=6.0.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -15347,6 +15461,15 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 23c2eac..96b328c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "envalid": "^8.1.0", "express": "^5.1.0", "express-session": "^1.18.2", + "groq-sdk": "^0.37.0", "helmet": "^8.1.0", "hpp": "^0.2.3", "ipfs-http-client": "^60.0.1", diff --git a/src/config/index.ts b/src/config/index.ts index 8b5a3b5..9ec033c 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -9,7 +9,8 @@ export const { NODE_ENV, PORT, SECRET_KEY, LOG_FORMAT, LOG_DIR, ORIGIN, REFRESH_ FRONTEND_URL, SENDER_EMAIL, CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET, Agora_APP_ID, Agora_APP_CERTIFICATE, - B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_ENDPOINT, B2_BUCKET_NAME, B2_REGION_NAME + B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_ENDPOINT, B2_BUCKET_NAME, B2_REGION_NAME, + GROQ_API_KEY } = process.env; export const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d'; // Default 7 days diff --git a/src/controllers/ai_appointments.controller.ts b/src/controllers/ai_appointments.controller.ts index 4c8436b..c520823 100644 --- a/src/controllers/ai_appointments.controller.ts +++ b/src/controllers/ai_appointments.controller.ts @@ -1,9 +1,10 @@ +import { HttpException } from "@/exceptions/HttpException"; import { RequestWithUser, USER_ROLE } from "@/interfaces"; import { AiAppointmentsService } from "@/services/ai_appointments.service"; import { catchAsync } from "@/utils/catchAsync"; import { createBilingualError, ErrorMessages } from "@/utils/errorMessages"; import { createMultiLangMessage, SuccessResponseMessages } from "@/utils/responseMessages"; -import { Request, Response, NextFunction } from "express"; +import e, { Request, Response, NextFunction } from "express"; import Container from "typedi"; export class AiAppointmentsController { @@ -11,13 +12,18 @@ export class AiAppointmentsController { public getUploadUrl = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const appointmentId = req.params.appointmentId; - const userType = req.query.userType as USER_ROLE.DOCTOR | USER_ROLE.PATIENT; + const userType = req.query.userType as USER_ROLE.DOCTOR | USER_ROLE.PATIENT | "MIXED"; + + if (userType !== USER_ROLE.DOCTOR && userType !== USER_ROLE.PATIENT && userType !== "MIXED") { + const error = createBilingualError(400, ErrorMessages.INVALID_USER_TYPE); + throw new HttpException(error.status, error.message, error.messageAr); + } const objectKey = `appointments/${appointmentId}/${userType}.webm`; const isAppointmentExist = await this.aiAppointmentsService.checkAppointmentExistence(appointmentId); if (!isAppointmentExist) { - const errorMessage = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); - return next(errorMessage); + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); } const uploadUrl = await this.aiAppointmentsService.getUploadUrl(objectKey); const responseMessage = createMultiLangMessage(SuccessResponseMessages.UPLOAD_URL_GENERATED); @@ -29,4 +35,39 @@ export class AiAppointmentsController { } }); }) + + public processAudioAI = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const appointmentId = req.params.appointmentId; + const { doctorKey, patientKey, mixedKey } = req.body; + + const isAppointmentExist = await this.aiAppointmentsService.checkAppointmentExistence(appointmentId); + if (!isAppointmentExist) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + let finalScript: string; + + if (doctorKey && patientKey) { + finalScript = await this.aiAppointmentsService.processSeparateAudioAI(doctorKey, patientKey); + } + + else if (mixedKey) { + finalScript = await this.aiAppointmentsService.processMixedAudioAI(mixedKey); + } + + else { + const error = createBilingualError(400, ErrorMessages.MISSING_AUDIO_KEYS); + throw new HttpException(error.status, error.message, error.messageAr); + } + const SOAP = this.aiAppointmentsService.generateSOAP(finalScript); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.AI_PROCESSING_STARTED); + res.status(202).json({ + ...responseMessage, + data: { + SOAP + } + }); + }) } \ No newline at end of file diff --git a/src/routes/ai_appointments.route.ts b/src/routes/ai_appointments.route.ts index cc77848..80d6580 100644 --- a/src/routes/ai_appointments.route.ts +++ b/src/routes/ai_appointments.route.ts @@ -18,5 +18,9 @@ export class AiAppointmentsRoute implements Routes { this.aiAppointmentsController.getUploadUrl ) + this.router.post(`${this.path}/:appointmentId/process-audio-ai`, + // AuthMiddleware, + this.aiAppointmentsController.processAudioAI + ) } } \ No newline at end of file diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index e335ddc..175ab4b 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -1320,6 +1320,18 @@ export class AppointmentRoute implements Routes { required: true, type: 'string' } + #swagger.responses[200] = { + description: 'Agora token and channel name retrieved successfully', + schema: { + message: 'Agora token retrieved successfully', + messageAr: 'تم استرجاع توكن أجورا بنجاح', + data: { + token: 'string', + appId: 'string' + }, + message: 'Agora token retrieved successfully' + } + } */ AuthMiddleware, this.appointmentController.getAgoraToken diff --git a/src/services/ai_appointments.service.ts b/src/services/ai_appointments.service.ts index ff44db5..54be8a5 100644 --- a/src/services/ai_appointments.service.ts +++ b/src/services/ai_appointments.service.ts @@ -1,14 +1,19 @@ -import { B2_BUCKET_NAME } from "@/config"; +import { B2_BUCKET_NAME, GROQ_API_KEY } from "@/config"; import prisma from "@/config/prisma"; import s3Client from "@/config/storage"; -import { PutObjectCommand } from "@aws-sdk/client-s3"; +import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import Groq, { toFile } from "groq-sdk"; +import { FileLike } from "groq-sdk/uploads"; import { Service } from "typedi"; @Service() export class AiAppointmentsService { private appointments = prisma.appointment; + private groq = new Groq({ + apiKey: GROQ_API_KEY + }); public async checkAppointmentExistence(appointmentId: string): Promise { const appointment = await this.appointments.findUnique({ @@ -16,7 +21,7 @@ export class AiAppointmentsService { }); return appointment !== null; } - + public async getUploadUrl(objectKey: string): Promise { const command = new PutObjectCommand({ @@ -29,4 +34,165 @@ export class AiAppointmentsService { return uploadUrl; } + + public async processSeparateAudioAI(doctorKey: string, patientKey: string): Promise { + const [doctorAudio, patientAudio] = await Promise.all([ + this.getFromB2(doctorKey), + this.getFromB2(patientKey) + ]); + + const [doctorTranscription, patientTranscription] = await Promise.all([ + this.transcribeAudio(doctorAudio), + this.transcribeAudio(patientAudio) + ]); + + const finalScript = await this.mergeTranscriptions(doctorTranscription, patientTranscription); + return finalScript; + } + + public async processMixedAudioAI(mixedKey: string): Promise { + const mixedAudio = await this.getFromB2(mixedKey); + const rawTranscript = await this.transcribeAudio(mixedAudio); + const finalScript = await this.formatMixedAudioScript(rawTranscript.text); + return finalScript; + } + + public async generateSOAP(finalScript: string): Promise { + const chatCompletion = await this.groq.chat.completions.create({ + messages: [ + { + role: "system", + content: `You are an expert clinical AI scribe specializing in rheumatology and autoimmune diseases. +Your task is to analyze the provided doctor-patient consultation transcript and generate a highly professional, concise medical SOAP note. + +CRITICAL INSTRUCTIONS: +1. You must output ONLY a valid JSON object. +2. The JSON MUST contain exactly these four keys: "subjective", "objective", "assessment", and "plan". +3. The input transcript may contain Egyptian Arabic, English, or a mix of both. You MUST translate all clinical findings into standard professional medical English. + +CLINICAL GUIDELINES: +- Subjective: Focus on the chief complaint, history of present illness, pain levels, and specific autoimmune symptoms (e.g., duration of morning stiffness, fatigue). +- Objective: Extract any physical examination findings mentioned by the doctor (e.g., synovitis, swollen MCP/PIP joints, range of motion) and any lab/imaging results discussed. +- Assessment: State the suspected or confirmed diagnosis (e.g., Rheumatoid Arthritis flare, SLE) based on the context. +- Plan: List the treatment strategy clearly, including medication changes (e.g., Methotrexate, NSAIDs, Biologics), ordered labs (e.g., CRP, ESR, Anti-CCP), and follow-up instructions.` + }, + { + role: "user", + content: `Here is the consultation transcript:\n\n${finalScript}` + } + ], + model: "llama-3.3-70b-versatile", // 70B is highly recommended for complex medical reasoning + temperature: 0.1, // Low temperature ensures factual consistency and strict JSON compliance + response_format: { type: "json_object" } // FORCES the output to be strictly JSON + }); + + // Extract the JSON string from the LLM response + const jsonString = chatCompletion.choices[0]?.message?.content; + + // Parse it into a native JavaScript object + const soapNote = JSON.parse(jsonString); + + return soapNote; + } + + private async getFromB2(objectKey: string): Promise { + const getCommand = new GetObjectCommand({ + Bucket: B2_BUCKET_NAME, + Key: objectKey, + }); + + const b2Response = await s3Client.send(getCommand); + + const audioStream = await toFile(b2Response.Body as ReadableStream, 'audio.webm'); + return audioStream; + } + + private async transcribeAudio(audioFile: FileLike): Promise { + const result = await this.groq.audio.transcriptions.create({ + file: audioFile, + model: "whisper-large-v3", + response_format: "verbose_json", + language: "ar" + }); + return result; + } + + private async mergeTranscriptions(doctorTranscription: any, patientTranscription: any): Promise { + const doctorSegments = doctorTranscription?.segments || []; + const patientSegments = patientTranscription?.segments || []; + + // Tag every segment with the correct speaker + const taggedDoctor = doctorSegments.map(seg => ({ + speaker: "DOCTOR", + start: seg.start, + text: seg.text.trim() + })); + + const taggedPatient = patientSegments.map(seg => ({ + speaker: "PATIENT", + start: seg.start, + text: seg.text.trim() + })); + + // Combine both arrays and sort them chronologically by the 'start' time + const combinedSegments = [...taggedDoctor, ...taggedPatient].sort((a, b) => a.start - b.start); + + // Build the final script string, grouping continuous speech + let finalScript = ""; + let currentSpeaker = null; + + for (const segment of combinedSegments) { + // Ignore empty segments + if (!segment.text) continue; + + if (segment.speaker !== currentSpeaker) { + // The speaker changed. Start a new line with the timestamp and name. + finalScript += `\n[${this.formatTime(segment.start)}] ${segment.speaker}: ${segment.text}`; + currentSpeaker = segment.speaker; + } else { + // The same person is still talking. Just append the text to the current line. + finalScript += ` ${segment.text}`; + } + } + + return finalScript.trim(); // Remove leading/trailing whitespace + } + + // Helper function to convert 65.5 seconds into "01:05" format + private formatTime = (seconds) => { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + }; + + private async formatMixedAudioScript(rawTranscript) { + const chatCompletion = await this.groq.chat.completions.create({ + messages: [ + { + role: "system", + content: `You are an expert clinical transcriber specializing in rheumatology and autoimmune diseases. +I will provide you with a raw, continuous audio transcript from a single microphone in a clinic. It contains both the doctor and the patient speaking, but the text is mixed together. + +Your EXACT job is to separate this text into a chronological script using context clues. +- The DOCTOR typically asks clinical questions, prescribes, and uses medical terminology. +- The PATIENT typically describes symptoms (e.g., joint pain, stiffness), answers questions, and speaks colloquially. + +Rules: +1. You must output the conversation using exactly two tags: [DOCTOR]: and [PATIENT]: +2. Do not summarize. Preserve the exact words spoken. +3. Do not add any introductory or concluding text. Output ONLY the script. +4. If the language is Arabic or a mix of Arabic/English, keep the original language intact in the script.` + }, + { + role: "user", + content: rawTranscript + } + ], + model: "llama-3.3-70b-versatile", + temperature: 0.1, // Keep it very low so it doesn't hallucinate new words + max_tokens: 4000 + }); + + return chatCompletion.choices[0]?.message?.content || ""; + } } \ No newline at end of file diff --git a/src/test/backblaze.test.js b/src/test/backblaze.test.js deleted file mode 100644 index 17e8e44..0000000 --- a/src/test/backblaze.test.js +++ /dev/null @@ -1,72 +0,0 @@ -// test-upload.js -// Run this with: node test-upload.js - -async function testFrontendUploadFlow() { - // 1. Configuration (Match this to your local server port) - const appointmentId = "dbd33650-7a88-4988-94ec-0c38e4a9ab07"; - const fileType = "DOCTOR"; // Testing the doctor's isolated track - const backendUrl = `http://localhost:3000/appointments/ai/${appointmentId}/upload-url?userType=${fileType}`; - - console.log(`🚀 Step 1: Requesting Presigned URL from Backend...`); - console.log(`GET ${backendUrl}`); - - try { - // --- STEP 1: TALK TO YOUR BACKEND --- - const response = await fetch(backendUrl); - - if (!response.ok) { - throw new Error(`Backend failed with status ${response.status}: ${await response.text()}`); - } - - const data = await response.json(); - - const uploadUrl = data.data.uploadUrl; - const objectKey = data.data.objectKey; - - console.log(uploadUrl); - console.log(objectKey); - console.log(`✅ Success! Backend generated the URL.`); - console.log(`🔑 Object Key: ${objectKey}`); - console.log(`🔗 URL: ${uploadUrl.substring(0, 100)}...\n`); // Truncated for terminal readability - - // --- STEP 2: SIMULATE AUDIO BLOB --- - console.log(`🎙️ Step 2: Creating dummy audio Blob...`); - // We inject fake text data, but tag it as a webm file to satisfy the Content-Type requirement - const fakeAudioData = "This is a dummy string pretending to be binary audio data."; - const dummyBlob = new Blob([fakeAudioData], { type: 'audio/webm' }); - - // --- STEP 3: UPLOAD DIRECTLY TO BACKBLAZE B2 --- - console.log(`☁️ Step 3: Uploading Blob directly to Backblaze...`); - const b2Response = await fetch(uploadUrl, { - method: 'PUT', - headers: { - // This MUST exactly match the ContentType you defined in the PutObjectCommand - 'Content-Type': 'audio/webm' - }, - body: dummyBlob - }); - - if (b2Response.ok) { - console.log(`🎉 SUCCESS! File uploaded directly to B2.`); - console.log(`\n➡️ Next Frontend Step: Call your AI trigger endpoint:`); - console.log(`POST /api/appointments/${appointmentId}/process-cloud-audio`); - console.log(`Body: { "doctorKey": "${objectKey}" }`); - } else { - console.error(`❌ B2 Upload Failed. Status: ${b2Response.status}`); - const errorText = await b2Response.text(); - console.error(errorText); - - // Helpful debugging hints based on common B2/S3 errors - if (b2Response.status === 403) { - console.log("\n💡 Hint: 403 usually means your AWS SDK Signature didn't match. Check your B2_KEY_ID and B2_APPLICATION_KEY."); - console.log("💡 Hint 2: If you get a CORS error in the browser later, remember to apply the CORS rule to your B2 bucket."); - } - } - - } catch (error) { - console.error("\n💥 Test script crashed:", error); - } -} - -// Execute the test -testFrontendUploadFlow(); \ No newline at end of file diff --git a/src/test/doctor.webm b/src/test/doctor.webm new file mode 100644 index 0000000000000000000000000000000000000000..343c3b67277048305928c9d3d700958e047a74ba GIT binary patch literal 232136 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%0ANu&KaJ8E(<51$2QHcQHaEniA|N8mB(^j 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 literal 0 HcmV?d00001 diff --git a/src/test/mixed.webm b/src/test/mixed.webm new file mode 100644 index 0000000000000000000000000000000000000000..1af0f8014a5f2c2c34d2afe398afbc9d9a4782e7 GIT binary patch literal 233102 zcma&sW31?07cT5&+qP}nwr$(CZQJ%_bH4xInMsq5bf%-T$(1%UH_&}<9{s* zsMZ>T;e`Z5B16Os0X<~{!h|iH>}^fe1^>U3|8?%G>sbo~bRG%ESK5h_)};y#1PTii z)R$LKRW>e4mUnP>!<@v_;Q#qu{r^k^Wc*_}nkD|(kVtu_ z3IOwRw`J-JBwki~My6>Q+5YLTff@b4t_pw#*XH!@nsShofmzeE_qzqQPgqKewE@MG z(a012q!~ZLit4M6W91oJORIoo4a2j492wu9Nf8OYD`#Nx`PUtuZtG-A$oH$^W0?V) z!mt~L!*<4U_mcAYY2*espr%0GL!eCpq^HFY=CC6oy*dp=7@8JAYz1_DhNoG-1C3V! z6vpK-7#vG5bHWZ8w-6iaXj{5rFnb#|(@ok6>@{5r!T2IdPVQO_%)PY0vJ3tRK*2cq z=uEQll{UEP^rP{{`7tj?`RQ4!I+Au82g^v4eI)x-^8-(9&bQ^Apz8M6X#-=dp03sQPEpN$s}0PU>#=Ei43 zLKNf`@F&pu8UE;vH2Be?PCkC3md?;|*wy%G7a#cRy)nww9YfIpmc?+f!!wat8t|HV z2>?6m(2yhzQ#h;0jpwlnaJfH@Qa;$7r~-y=sVpY8KPFjaoDE(2?v z=XPf>A3~cKq{oBE1T(wIrK0pQc2M4o@p)jA;>DD0y^iqQu|8&nd0lu)&Lg-94r8W< zyj{IoN&0;|LgfXQ+3x0lih2GiHe?wo2K45?f9osO@R%0VIz2MMxULgp!E0d2qx(`v zA75x?#yVC?FTzg0&>HtOqG?FYg-?mV+{F^Xsl4`I9a;(7QsiN}2&p zYLU)o7W+h^277BZV_hxRQsUMqFZZU0+682f`v%W-wXEYGQQ8sy3dE4HE#JcbvElTJ z`py2qMRsvcNwQC;X3!!d?w9c97l)jp5-`!7m+)|_T~V(HZRoIY?nKmGMOAdHU^~SE zZiRSrJZ2#edXA5T%!le51=w69FsbnPhkOQED-M0Fz*-$8wnmI0jm%&4X&wfpQdPC? zrOBJH01wokv$Qg!j*Q_nJe4&PFhRuqgDT+}>Cpl9D zRBU9@vg4^a4l+{IE00fbaFugN)xu-NmAo#eeR5FW2As}}637WUOoapPd%#Y2wg`4n zn}WdAWkiGy;Cr7Lt)FVF4jienVSrC}u}p~SoXGA%cqlsFT{wGZU|idMKF&{crQn#$ zWUG^biW>!$qA12HIx&e1x}Y9i@H6bxaz#mLeZ4Ifpr5x(TVpzL9!d)z= zHe}Jc&-QraKS}Lg;{rm8QETKz>?VKX@6{aoh~(P9l@cmOi{AQP5bJ~e#IYrwnF(SF z2*8j{8)34eSx0t}MIR=K2HX6e8Ghi9Rhkg>g|=|QV@m~3D1@n-67uvAk~n;+>Yy;2 za-l|>HfX`7(om|$=82TUzy{IR5C0b3V{&6gkliVY+8Qq7WvvdQ{_d|)G&(2zZsAI>2V!nqC7Z-FHpW4+;%+&N$B zQT^2ZOR<*QgYg>0H3+U3MBu_m{O(WD-bRcH6vwO1M(M2WMG68*BJY&3)jN zQN|ypkt9`XBR2X8v(CP!#?^yww75gsJ-Kj&UD>(fqM`Fqm%Q+t0gt6$$+vFUd6&u0 z^sBThgW^A_SzGRh0hLhpBn*u%*slZ9q2VETT)&GLas56VD7kej*;&Pq*XgPA0!6G( zi4xXxzN7Gu%W}pWytP8qQ``;w)S8@E8Ns5RNiuu&%0xX&*fl<>8S76)OARLl2tlz%uHh6$*sSo(BOBi@y zgv~6qd??}B+e1PoMy?eJ0Pg(e6Y?@?Nzn0i9`8A8l`)Fd-1xGc$eFyi78>bt*(aNr z5fA3Ra-83&+Z<5F8x4{6rRIycLEWfvm_}mXdzA@MiPQBE{QbVjF4R{_T4$On>72lg z7%0zor2^y$^S|2#e3h2R2g#8-Uh%TL2n;?%DcWTH-up}nO<~dop@i1#5j#+R zv6r!%j2!GN>|IBHY{GY;q+Ur82h<(ek39vkyZv+;*jJF zmBoBfJjh}EC?;!;n78%#rzwN*)if=TOkdv*R;n$OgCv;D8{ryhak5hVHXF zV)%&=K;)ktiaJI8s1g>5$Dgzek61ue1|s8-q8#Tc?uB-_RU)Di=p6)kJ+txm+vJ&C z3xt-{xogb4WZIJ%lJkGp+)+iL#Z1fQXtWl3mQMpsnLQ_UXZ0pIldue@xV-12o&Kk|<)32j+c*yqEGJ)%^}lGL#YNs$+xP?A1UKLYKZfb_ z+q{&89+M~Z7&Ie7%>@WQ*;{mrbV95HA-NJEo^Ko~*L&3$&yF~3vgoLmbFVpf&n+ZF zT$Y}k51~9nvNRng_YGie-f#>3IQ(OiQv@zW!MVhHW3a~jB=T!p`*hS6n-&!l3M0E7R#M|f zU(oD$sQg{%lBlxh-#|!1-a+Rs5wBIjrt1 z3`YQq5P+jGbYxYUzjdzOyew=ljiH^&PR!|bxWHqvVMsZ;Hb+&HM4N@5|rKq>V`>Ag2ZBqEgyC^dyq&N z^&0=?O~c5|L2^+6^2l3mPf0L`bxHMGeb)1G>9Cw(!}WOehhiFx{6Tl@mvMklpZD!_C4+}NyPSvDmi?SNA-!DW z_`EQ=?QLx8P=)jB#)3=pqUL?{zAL3L&gX0H82bZ&pQ11B9D{sQ%x(b`%Dz||NMfaX z|Lz-(i`@TYzORa&+OeM4obOC$E{f^hn8QFS@~C)J%~IXuuRiX_5lYcD=*D<$4k^FL z-8s1B&FHpudk*#0EoNM=dk47Knz=4$%rs&nEo}F)C3kh_Ij$|l;HX2%8C76{)O#W1 zgf*5ibBt(jDsT`y{A1Ss9DyBw0H?TstSlIj?ns|Nazex91~Qrt4XrN;#ppf=nyQG1 zD$}^M!?V|7#{3C)SMhvZ=|F1lPv`8Hkk78a_lTA|Jbpt(_dP#OjfA8t*nf)e|0&ku zQy(U94cjy!xR2nqo2DH7B+8O_cHVqv*(D}ELU5Cp0HW>hAR>j3M2SPaGF;0{O(y~< z{b@UTzRrZVsC~LgCe=Xg9%|Zu>_$%UH8fS^oi3Q_D2|5bYChAr1|42mb zrkd?f8C#7=WeDs~f=cqKsznIV=^^BZN8dWH}$amV9-koy&s+1Dt?9n z9wk116_o~^UF&yOg!^KBq^e^wu_!4!2g9C&ZDR;ok4CFMS-aiSrS;Qfd1Kg6g~l%t z_C%Syt_(-Bb$8|qNEdVK?v08u5snIE$T>qYJ5gXc496o^k$lf)vxi@7A{KU3h6_0y zD^DOugQv$~PMieE4^Ns1Y1&!41GCfhgoKp#$;Y@||KfMd%@iHXXS&RN4>IBBsgs-A zg)AIRUzMO7SE2Ycjb4uIE@*X80I;st-*#gBahqDSpNM(PCfb2bSGL8KoJ++ljgJ9R ztu^s07MN1aHyl#HrP^uSx>aS=`yq3Jg4hf1VT0t~-?LP@NJKQV^aGck?q~KF>a^wz zzm6qCF{SMoAccrd=q$SqZC5WvVLOMet2*y`-Ff=i82b)cIs(NZNZLB^RCvP?e7v`c zRO(j>HVf)l)VTzd3s!2iZuR;*31FAqqSg(p1ikWw6so&kRk4-ygC{|rkOf|JG_o5l z`h+40lYolXQsAj1p{gz|5ZUHtdx@(rCMUx2j9O@Bv) z1L_*&t0ozt)uBB}lzu6qghy}$XuKSvaa4L_RR>%jz0^u?9-J}3+L=H#9G_aoI}R-R zR9`e)6798F#N)`%i`BTF<5vU1bj`fKZY+^VIg09{6f z8MYK48)EANcT8r*N#q3soGV%xr$TclAJDj{$X89ut-KgCYW)J)EyDN3v$`<2!rB2w zHh^)eTkdGMeLY9+hFXw@$K&!QAIKcYVPa#DE(UXQROr3WCz0$C_!GJ=*V#J@JUso9 zW-sspuM<{IWwB>@98|?kh!5#A|$Xk>tTDyZ#JZXYpu1Rn(+XDNJ-l zE`KpoMUykk?TlfRcl_^VEG-rp-Q85 ztj!>`SiU+GnAM1Zr4N(5T1XOMC&}g?j_4BmmY_zjVjL3MC?ZRvCA?MhxB^ zE)ksj6I#ZBJdQ4f(A4pQ^Hu{|*A4ibpl@9oJs_Na)7_V%EcGr?XAusHkWdg(7MGyA zg+BQRu=)YR0oTy15Ic|n3@p{m%ZVO0XDcJ%ru9i{K6{Iz9s&-+hN@uSWUp9Lom=L8 z?B!KX_@h0b-Na=rMVHNh%0eij@o0xmyR_`oXvq$qmdx`Fp4U%~bNmaT&ch2R&w_`^ zz^-g>KT1NEvbO6TmM`VR>B=ndilffMT(k)54WHQ7)0viIRv*n0@Y+Jt>PPNqzb(Ya z0aqYF-Z~iew>G|p0!_9N z38icl(iDHpaDEe!uSk?(v!dKV#pzj$T!jtIX1oZXlwriM)P&V%=nMnyLUBU)?d0T| zd#`yHxy4E2!d%rF<;bF!x1&B8l$W;y>DBoy50$7f@Y{}fAUvTfZpB;EAV~jM<$7$h z^T1S)^khf{WzE$)s%o)yT@m;kiXiS)rG9eDer+%Q8MRD)krj+@X-3oz%iF4sC8mN} zC<-HZM<7iK>%oqYn%to?&S-|m>Q9I25HL4ZYB+#TO~6h1G~cFGX2zL@MU-q^rrCQh znK%FtlM}mrq09;T?eZrKP_4iqXFBxL0WU`UsWkux+j6w*IKFIhLUxcUfShseR{SmX zgCRWWaI+~MB9v+&f{dJWJxp?7fSAP-?cKK~x2eN|$7c%~6z@1Wrf`Gxl75GSb%~Z` zFtNYIxGdptDk})L&)7=@(|Zokjl6O=*-5gz-1#>OT88s#^45WL2PEHsd&U30#ef$7 z6nnz3R|BXocDkWEzb?JZvE72W+F2_zg+-KjMFYHcqiR+#*PK+IkSGATi>8_tF(O<; zO9J_?9M%-u{po1b9pzN1pE{O;?jj#3;h1WjjOF?+>DxL3j$Oz4lKK9(^*LDKGr3fZ zXQyjYXs}XGx;)xlmm{Z+!nXoS2bBX?r0$Wz5}4pf+9iERJyWzXi~~&X&JzG_0ci@6 z@y#^I00h(T1P}+yg*sqQAg5)39ptjm$3)E<_ez)pTlR&YNl>4nl!|M@a}aIV^)Sg{ zt_vdei+%0BP#Gy8E<#7+b64mGuw=g$LuSXZ0rBm`Ksc-;nO-@M6S;r2Q7RIM*ORD2(_ACT@%6JHOy&y9_e6Mi1-O{RBv~sRXthj zrZ$)qn}Nol5_~~`;|)-YQNn_U1?V?uXKJ5caBO`~>p?R|9*S&n!PFtHV?k9D-VJZP z9i80j3O2i%#0|s^bcYiLo=dAE*3ryFhitn^c!AN~?r2>j0?Wj3Q$Qsi%Z9Y(%t*x3 z2gQLS<~4sKI1uH;cy@mJtf`B;adI&Z3s0i>a+;~i_KF=FX9ig_GFDe^x0jjHbQW2G z$PKN8i8cF=6%;Epotbs0-)MHu6|DrW-Clp|jcO6lJ!u?Y@#b)J#YQ6KYhy1*A)G@l zLU%RNCJ`srko*~SwU1A(uQy`ai|D3u|J|PmnwJzdVJYRXKZ=em$oC2^`Ad`+7x0nF z&SDTk_Ms;-KKD)O$4^HYGH5;qm^{b!Nhfg#DE6vs3WtS3KBE*hw(6j>X8V2kg6uDZ z7)N2VjjUCip%}Q2cV*Wpe+$4$Oof$j&Dm>p4l;nT+&tl-Av>hYSC48bSp09?CL}`X zGgCG>z0Y;0B)i*!+7^KD`5>K4iBOd3Dn209mG=q@8a(L^5=gYGi^+sIPVqU!DwZXg zImOpwoMo=NaLtg5n%CP9j6bR4Nhn}WABO@41F?`3HDvr3eOW7X?vDhGhUOlh3d(Mj z(>MVSm~WwfPr&F6Gh^1WKgsu0hG;STtb@p#(j~-EK2Qo0G&yqz4?F{XR}`vDaoKg$Qf@Q(W?&V&m$Zr37cw=M~0}VR?H)*WU+~C;YnE z@ER7mC@zbmNvj|_L<*qs0!Li9rLA9<_`PLr!7HvK2!u>?;;jjh+IkV{>2mT4krui# z?d35NjJHO&R7>DRp+xHX3KXT-jc6ah(gXk*pM}P+`-VLfExv^*)*^m{USnFpK02B2 zVv*cxWlSarE*r9+2CqvH9B$8ticV%U-bI*Ens#6-CRhoSnRwXu*wm*q7Wh|PkW3sz zUis}+H2aP-7l{qe*qF$|C!t{M9r{G1;y%x><2^+oYD{cKK&kaAM@ZEyTd^jf{4KqE zEzH6Av{w4{t$K#S!Ri*w4i+*7V}3QI(X9Gi8M7*Y+Gjk3z!G0xd?i7c`d*wBB9qs@ zoU8sA>P>~}7Itp8+bJJw%YdpY>`hEc4RYIe9lR$=PY!IaJ+T^h$ecoG`!EX$(L}Epf6o{a^|k`()r@tS?33 znen7PhiArSxr=`1e@I&sBag(`m%M?-gS;}sirwItcD(A>7Y$@^FIgUHs;#)yc_lD%JVm?V$E%k5oLdD&)4ps554DQQRhY}QP_%l z+5=TzZ?u#DO#VR32@l;NyG9$Z*rbE$ZgTTcVJ`-onS7zLGw*gCJ4zurM?snwbG!2e z^pWo(j6^qQgfWW%8r$*sJt%V-)nFZeX5_TOFUbiL!I$bfP$fvNptIG!9dYBvy0&17 zlXHCawvT!jm&eANHrnOisING46wmymb_VMqsi26vV)2ftLs$9w6P!67#SKRR;^4r) z2^{f;PbVh|Sh@&CfDRwWJJG`cv2j+6^WX`ThG`P(E6cb9hhyqp>}yEqF3>TX#%UYjZ954?M8Llf8q<>mrmZ zB?{z}#Z-5uF&!_vlvm%)arX;O(Me9V{8R}t@wK?|11p8Yb9A@0_Ob5-3_d;i*tkbf zgbw>%7_?l0fu53SGbK?0Oks&QqGm^v2G_n_&)UATIpru%tOQv3j;^qU7*g;aUt?rD zK>Y4UYQ4i18_s)jw6A=IqyT)Z7*I6m-hN;j{>bL0&!Hf|`Q){xj3u7o)Ag;mmUTVE z0(Wz*3b@T~duDB(3YI&#v$uV5&)-P4Ari?IS|W?S;7`Y z=MEt-emN^N{#0&jVuG8k&xCL#^YVQdfrlw+-hm(#7nKv!>S zQ0K0(N22D&;+osX^D#;=KVwt2)I*D?aIKFO-`PE<$7Q(S+R?;=MUCH-8Ix2vi`ya$ zxTw*lqb`LXN(+tMd(clXrUEh!1R+?Ikw#z zJS?FF*r|XUb`^~@PYW(37HWAd7T4H=DjZkfN*!eJfHv2w4#Yc>MOf=o$b?wLRU`>c|%1 zYAKu)25lizh~dP^uCprGlxUX=CTU7r9u*$DG3$f>ju3i3qY%lwi#|BCTeGLQm5-27 zr>ZRP6&1$c|6Inf&cjR##jY;0 z3GR)kM0Ed3dm(|UEDKgZma>befPDRwhTVE^TM7ri{2Zh;{uwf2`x|vHbw|_gk9T0n z(-_dzld4M4kvE2-3E`orPdO1M4hrC2-Ev%WI=%Uwmmgiojm9z-m1(}5^EuMp4n4{lF zrNY|T?0od1sqJI6%!h~!2b6o{;QPSCK-c3^f1kK1HGFnO#(UD6=~lSM)lSiz{jO$? zNWQ?WDU~9P0&lXCD6~aoj&2Xz$=oR58rW3qbrbBX;vg4Ivd3!k!y)7AJofvo_;Jl# zJ%>XQ)vD6Po3pl!f{Gs)6+~;ozN}H91}R*j96MrsKV9b!ffBw@_ zq(9)ZDcx$gQA#Sk$rg;2Q~2dwYzdekKr}$%JSsd^372;$eyzD$n<@iOBmne)8reGg zBXy;7DAXdczO*dbfN<~U9QBG_9E!oe%hH{)$&=)>XW+g#Xj7sWt=7>y3JKYm^2nqGnzv5=%%l_*ZDz9(ObgvBJN&xXDBL zCc>O@wVGD*>HTdk2_c*`^^7(KPdTe!7FzZ?Gg+M14Qz#Gm15m_1|iUrfh_mCKn@I? zO(>90mBoS4R^KchaJBfYPz*y5#F8W&8$P#0FS%(U+X=+`4j6w>A}c!3b7gc#i45rY zsL1vN9zcB^p~j3V0ak*>gb0g?!FsQn7=h}nVH}5@-fSQf00-8vToj)K>}&?Ssdemp zD2n?Obd^!s`04wl*YBc|f`o)S{7g}0&Gud@ikgUronzf`oCv?f8|#=TUIE!BH@&B57i9RI5JtBo7&AXQ zXwa0_-}`d~h0PV4zR67kslY;%)F)gxe>Ij)>MTC+(%bGIF6U#_4!?AQt)VNVrno%L z!7cPRk|~&I8mX8{ggH*w?B+dEGAD2^Y2B^*>uAI^#i2_I0o=@`&_$_x+gG|J44Tuc$G z{}gNdQ>=h97$9L5(sx840B9z`Z(6{hB4&Azek*H1`{;C`B>ceN&;X%iQL`zho5g_!>oznBp_#lBIZ@c;;h2!e3`NNnK$7B=c3cpy z#;z(#Q1o~1LRM%cd;e-aiWRaNau-PLK74ODUt(PMfbX`zSOKL5+YQTw;(oWMWRU$N-W(q|Se zgCOSkPn&v_7^4vtn0LZoqg($pu>`wnGY}j&VzkP$I=svK5UK)@wYefNxyTxi9nCt& z$&PCqy{%O_a9_kZ*M9H+`_Eh1#`cQmjUP zv-;`{n0cJL=UwvV;8jT&T&WL`&Pq~Mz1fAnohPs4O&~h^NQX16YrPBAtzW#Q*Zm&V z7cw|NI4S?G;=>7@EC4{wJKK%cg|DY(vn$*@dyF)0y_LEMBMRI?TJAt`XxA4igllYk zQGr8r#AF~2cIkY+L~Ss(6&rAKZL_vr!h+HeWHabI>}7y^3xJ*kYU=Q;99sFmW)|af zChy>INnLE%0jUOUN~0Yb>u+vk!O&XU#BZE7){>-Q6)Dc%YLPt*C7+Sc8X@KlXX{cUcbR1>j8gVJE;^1dd|5*T2C3w zYdU6?-84^Z!1P(ith6?OY@O%tQf^ zj^E1i_x!Ixymoch1N2G9S^;yIQ=2e;t-b>&lJm*=ILH_?+Byd*!f<|J$g#S!JmEgAz~~q zg(im*KDSnz1i2&D%0LOw8iQIZjt4kZP9VRMHd27R7#f4@O9%9c9!{Fj7G1oh-6qrC z@VyCt)G>%zX!A)scnY44gGhP>%bcDTYz2^`%1r=L3LTi|l_Gbv-rBb*R&WKR1t-w& z40&MwthovI-l?#VN30RYtQ}hyw#H&%BT9*#$F9qG#yLRLdvrFn^`EnT&r;=@8EMTE z1^~Q)sP;Bb6|5=@7tu0tBty^ljge4k7yz5M9u*_a{dfpnNTOBjm^)H+(lg1?I(L43 zZjjG{6m$6=Psy%Y3UqxPyF>GVQ#Z3!su`7;3Q6T}YP7KX@wm1??=|N9DZEH<{<1g( zx*Jjid77(D_8XP4;di1BrT(p19=O3Li;U!BX3&Mdk$xeLm`F-9|FQ(l#(o%D7s|VR zAAF6~JxAK(GK1e*!WfuN-h9(T+ydlD5{Zh5UxBtTq%(#=hYEsSl0!O$g2B9Q<%Tjs z^)D+d8#B(rYEu&|_|!F|5*zoKeKQmyhD165FM&B6Ebr$q^7ZbjBSa*7i0zUI)u&X* zmEZita`5o8jActX-0c>tUaQq;xSVP2}KIvAYuL zW5Vu3F>e@1j^Qrg>uM0rA3)TfFfGV7Vt)}k;<#Vp%%B$S8<)Mg2G^D>{u1+P`3EqY zbJ}W85m)3Tx{JELen!Oa(~z>FXkiKK-RjO9DZFu*3q|l@mnfKi-*pT*YmQIDZYJF= zFj$cs(M|a49kYxC`)%YH1gr{+BFGB}8(~>QIh~Nw7FM>M(C4APS}h9ii^`>nzkJTO zZ-?w^-n^ApC$TcrU^#5a0PRF{etrQXWeN~FZe$2ERJDuF8v$j)f_g~-%u=&!CyDG6 z2;eT*X_NSr*-w-|hBAkipZfm+Og6u%3n~R7I}uB(2$V)GTzzZisw;3>`~fKQn7$PM zMNr8=R5yls9$jdjIfueZ6E@>Oyi2U+9n6Rfqg)RitVu!Nm@24Op-X^qE1cGoAe#ws zaNx2e{yMdVRm4r2PtAI9zIFb~J=^$Mz2>?<-ECZau=JAru{`EzQZdmA60>0cQHF8jbc`**rx`fPn zHBEQg`My??zZ;$k!$l6D-&L_4uOT(MV5H+hnp;hl7I%LyC3Z2_rPXy?!C&GhG>-O4|8G22J`e3JTrH4 z&U_U*S2^6_ty<+NAMaZF)--D1He)dt|3hZ|-H4ISIm|5TlQd=4K7AX-P4ZcOiwXd{ zyH2*dQsX4dzEunzZsw4EG#_=q=0t@&otC~PqJ=o-8oJGx=$RjyiHHcqwXda=M6xY^2)oh00tr9u+CaHke6{#+ zAFX`O(mK_yD1VMLz5nA^{1tmr5>WdyI-kKgC4w=6uN4v3$@CV#M`0zwo4+_>Rb>HGDN09&Kl`2OKW70lR@Xgj(OSVwT?Z{4Mj z9b|V*xd%O8gOgQCZA3aMeK{|FZYhSl&VA;S25Hl5nY2FpJ60iId!VD~@u5DlJGtgY z1%?D<4{rdsXs0@FcqrD+OtO#>tgXVM4WwwjyH+rsSOt#gYkdGX$ShS;1xBC9Zd+8C zX{`Ijd&wF=cs|QPzcC+O5#t>dPYhn3sB2HDS?yy6XT<5d+vK~)wo9?jQ78v#*U5O1 z&Cy{#!43W|E%r=+35%?&>=>aPW?u zCwumgw3~8`gR?5nOAacM&}AiOWim{)fYw8zWaQws4*M$$Y3~i)e8J7CBpi(hVfliK zhf-4i+bSN5TL^rJS3^0VJt(S|eg;w>E9?Z7wAQY8oC)wRV|_{{{0b32*q@}ws${D% zQp2JfDdRq_GfLZhSKv^EXj#-ym_S1yHo(*$%Rw%Xm{=Pic3V+>@RuOEgjb$Qvm&Q; zGNSd7)}F21t(-`Q(z4NhdNVG{>I27pUYQZ)@NodRAJ)|VD-Al)q*kRpx{KsTv0T)J z2t>CUl*|RY>da}AVt7z;*^PW?-`i&pC!Pzwl%h9*ZEa4uanz2!%)b58d3kX#cMkXS zX58KBU+^h9%N>vxM0ISSJD{{pc%3`VzMJsD+1txssx>{mI!P<-ImF-!qt%BwTX(nT z%t+9Z9$u@-@YAK>+3L?$FCcF`v>12k+di@b#^FH<5Fd^+w z-tZUo!wUZ?miVW*DQYiCaDjzK8P-}juh^|%iTO*RMx%W>;LmWO_z!g9Cz0hM`Ja`d zhP*CO4e@Dg{;N^v&erc>H5MhxsM537)L{ip+rSSw@JHp2Mt^1zsjVJs z;R9Kh^AUnYQ6vtBhc*Qxy4DEPz~Yn8_=-I&h+#H8HaHI-ChcTSQ|n*$ZKW<@9<5dq zIbI#fEUjSHU4l5Y0R5Furtad9x zH1hn%g0^1R-WK77J8Kj`N+D~lkXo~_n*%i9AoVDdatG*4gzwLu z#9J0mA)J_1w=-uTMvX()8KGgNE2;0e;XFyU_&EG z@lTef-M!r*VL%Hr1Mm!{o>m~wY{FE-(qSSV*J3{_IWAyKt!D7-)?roc>usBCPdFq- z@aN=L{92m5`u@QnEJi}td)M=*vArmOzaLadDgh!K4V?z;>pfbXZYMp^%8R{uN9*Ob z&BPb%dRQY+EE6)XjH1h*FZ;OmPGX7X9mHDgK)gbf+xJ#={%$K$D&s&V6_6{3-;$ek zaAvhE?OG|1S?@K9T;$anu7!rqWq~jgjcpK7k*Cr*(n=SVUvS)}trEn5TON5&urtmp ziC+q0tQ+Z*p6(^<1{U&^U^3?(CbgQczg?Qq1wHZ~I!e2)Zq*ViXb$n33r}11gpUYCBVOxVCg+l~lDvol0@tHvCHzF{+#S^`o~aMH{V$ZpJskp zKO9Db{!PU0Ar0P(7UWV7r?Ka)5V#sl58!Mte908(sCTv{-X9K)6R%~&hu_OD7Ml76uq^A-dp(HIe{Ej2*vXbFT!zBr}E zqPWfr{2hA5#~1Nd(_SzRaJ2-}brET!D#oOsQ|8_RZwj6G z4_DAhAbJs8?N1e;fmv$zECuBzmhCMtEUKXe3i%hT`$dn4dfF^g8($kCeIM#l^o?1{ z2b0myb&U>f-n3uS+y31WRyh)C7sB;ZYgqw@XIDorTFri*`r%R?!B?$Z6M@(r*(gGB zh<2$)JmSJf^}l5GUREu~F#Z)*Vz!+f_kkq`@37qrxtvXDO~(E^fHV@<6@^y9Nw#zP z&>wf=vI*>o*wC3bz#FPVo`l%H#V;|Nb7Kzj^0n3n5klq+O9zj;Q=7bc!1k$%UyU47 z+w!UiN;1bWM3f=$DJSAW?;~nEt$_*$c~9l})a)MXv7Aumv%u`@{#1fkOTyCY=%yo# zd1AX}%&9|$AY{lgR4jgi2c@PGzZJrkslZzBv^ar~)I1Qu{(V`Tp;Y6{Q`D%8Fgrrh z(S#eInO-c+MvlTu2+WPxi`s}c?x&bM014FUEzqj2W=LPSh50k zJ*{mth^-;_jq1|Ltr!N2zr$rF8n+X^n?}jE-oxWOq?0z&D8<}>ql=cmYJIE8!KyWf zC;dhCrTiF9ykYaXx*yuA_zVE`{+E2!42{F=J2N*;M4BTKe?W|7Ynh{K2_w3V-&S5H zv-Rp5S_|qm%xs8WsuNbQgUn5DViZ7n5fHD@@CwOMFFYMQz@J+)3BME1c7=dv7Ef2u zH&s*~@PD6(8<=)M{7-t&WfI_*P>gZLHk|W~O)J|H^>FAueIm6#_|dczKApn~UAJUVTP#|q|WIBg`O2#ID=zs!h;AebZ$tp&6n_f+w(GnCO|AjgbW6!V|8q#flJ zGio;E@vf&s4Fi#!6EU&&hq|HvYj=g`$S zoZ@R6ygj6gg`~0|0hkBBJzB6S{2hRdq!fnVPB-;XGDRZ@8(PrI8ldYBrDd1EtrBYT zCCIy?q|a7XSMJC2m15GHDm`!blO70;A@e3!lK^$gm|@Y6WoZh3*(G{rWYCI=68B2G zE{JIVb3ZU7^Pl4W|NT*imyWIzTB}XXEHy8{-KpPu zFQpQS+>Lk-29s?V1On541Uyo8prXc{H^LFey0QwV*_dd>nA_J}vH3Lu*n$tzh6C3- zixzyylnkeZjYcEnE%`VgUh_^83?pNPxr7Ff*j&YS4Tyi}3gyr?&6+X8Re?M3!L?P5 zT4lr|o=2AD?pRevqfckb-=b&bf&Su-!TAH(wXb$i^X9pD=KZ=6n)r61^}?cW-_n28 za}beloUVc>6hybG0Q`b_o*It`u{l2LodsL`ur?WVmvKLOeKf1`L3a}!hsmp*6s_}lVL+6g zEgx)j()l@#LNrao8szYFzK+PBXgA3M@+vyPUasvo&Q+8t8Z6&Y^wDF~pfQWmRS+2v zqy=jT@#7fg@bm)gJ>d7ASPvUTkkOp;M#$nqos(Az+}y03Ah-WXV{~<87YgmQUbCk% z+W=gdnr@Kgm5N-$xH^h_Hzmf zGK_8H;Il|jb#Nm1`&U(_6z9H`_k|U=2Uy`PZxK)zy)u%z71)_~4VhfSYpxl>Z(r~z z`r%yl4Bhf(0DgoQ6gRd`p}JM|pk;XN=ZHwC>qjwyOhSH&Yb0my56HJ+HGCy=CDG}j zEd%IxoJITIkw0oo-21N)plk z!_hr32evH%7>#Y)wr$(CZQHhOPHfw@t%+?XZ{Gb8tNPTb?r-}h8j{)b@FF70Tpc&& zB1ncZ8Y#bxV&1$QQlU*i>VXj8ZRL*rr}*oiV$D5l_PcuNy;or$TqUKexYLBfq=#&{ z;wQSZgJ07J7ZYKw>t(|>smnON1y}U60ff<~UXM59mdNIXtJO%*Z+HT6o)3!-9bVU2 zLkGm2=tY_(@4RM?zsnqBURFu*P~zD!kT@Zu6+)KJc@YG_GEH6oj-9!7TZ`O*H}Ct>Mm|A2>Q4Av9kM@mCz0 zy%vS(orTF{^&Ui^1*7>b>{R1kkIg)p|33T1fgJcm1GQ;s^SB>ywLj6H7dIBx?AjYw zflEat&EDMe&QEYr4Ln(^fwAcS{Vg+);ExB0`W~CBN7PXOdwCtro#UACbzmNk>E_zO zpK!K|x;M?~0gCV6;RdZH+lBL~Db?515!ARo#F(aN8HhlVX=DIy(M2cpdUV|xJ~i5+ z)XR3R`IXK`51kLD;Re$j<0|e?jcZdLit+gxJvMkFB`4kIo|9bg8G0h31=fQ@Enb5n zLz&c`pw)lzMzjm;@5vhj_3m3!6qXejB%RZcIe`)$G`Ehkn|2`3pzF?Y+diy}$V?UI ztIP?y<6%*d+zghhJH33JVFE!CZBI#8J4i<1SlHZDOKKl1)+*s1U4MO3%%}R8&>v0J z-h-o3=HLet#zQqZ>m;_v;7Mnk^Lk6#TnVJieLZO*gHkk;1YPA`G4AhQCRW3Kve84Pz`Jb9J=);3v=sKsk}B*!w_tuk(UqITHw+Vu1KF0J`YU;a^L`RZtsk7!S-zKcC?TNDiH);}kVO^;b&`_@BUyBB z-+O$fs&_v{H`45gF5vC+Y&MIpkNXSRr7m^Kx3P;6cgYj8#`@k*=}eST$4C0rXW-bs z@f&l=v(9!4{z%_yj{iFAa1Dk$#fo#b9oLKJzts^tsha}GcjA@!ZYRdJ8qsG>>x+4ge9vtuD4Kh3e9x05v{;?$6fAy6_nlaxlXD&!lmsg9E-(0(ExxodglF%hx*PX*LXj*+s1EA$50MIm&PMs4xXwkA^Ms-&)Ms-md$f2 z4@2lpoFav0&=1jRBc{rm|2*r-ueWxz3$4-Hf2)H&e|!atcYRp50@uIU%l|OL6I3bM_J`}@aTyqdz`05b+c|Ttt zDEjyZEL4lZhAjCDM+xo0e%$cFK7e=-H>TGNFzJ%It)E^O5$j=HPLNZ#T$RW9_8c4i zU8~`@1W|o5&n&qF3YO%xuV>`Fhw){X@zQR0_9t!sVES0HP!uuFBV~OE&3xQbZqMAr z?4G5g&P&KWk)U<79JQdEDaJ&sG#}jmMU_4K0(5}Et6XcPp_(3z>Z)g#s8h1i$fLDy zqQ-&moX|9X_wkzr=#MrPb?|m;itGJ*9{T1<#2|a|R322Q2AiGLkpi0kCe)7ws7vBl zyJmBQQVR8XWGYO)K83mXZhiaQIYf+$Rm(7$)!`9N>iCY((b0>it|`CLDO%(qAZ-|- zlFQcwDR~HR=WxDZ-q$mG@(Ptx0Rp#Fgn~g`?k?DViyZye zM3aT6ad}X&zl-#^7@(GaDcCq-|1PwqnFI*{noyjzMxs?aY(9OBcB#cKptAMIOEqwQ zr_tI`{MBD>g)XKUm=e}Re}Da3_qIGP($&21fbc+}9Gpz!s~|~li0Ar}9F!^@z5qGv zwYG_Vi)_xsq1E%8>YZBVH+y4~kROi>vjUZN&9>p+T6mAxI5Rqs7NHmw?uWft z89JcTX8t8Su$``?{5_t>6CM9A%JK!g66xFwf@RDiQCV?;?IDx(1%t;VB!n}(bjL+k z=Ip;Kju_4=w$a6?JvQ=H3a0_*-=&OKHEsLf9yN%|KgEjQrPSKYc<|*Jj-2b0EK&^4 z(jOlAP`U7BZIXAqIjTS6+Pw^~0H`);^|Lb?mrpZ*YgiKxIO(O2RlHNuOROgr5rR=p zRKE-{-_MwTF$uL-In(=}Nv?GfK+&GLw7nxWf&jpv&N4al6AOTyn~=U|UP8&Beyvc9 z0_2duCHQemrnQ;`6;lfAmhow-C{7$#_C|+4<8yo-0{a-EoS*CCO~bTG3fb9`HXKog zNuLjoS$5`5Lv-+E`!L%tbNeIHWbk(_p1Y)gZ?;iSiOVOZLf*x;)EiF;uwr=4a#6su z7v=>6aM{#QSk?1_@i@g~`!6%fwyLJRbRF1%N5*Xqe=w!YCWzJ@to{Vpj2;)x z;R0Eh;ugsJR$E2&^kGx#k0K-Lr)1@7iDAp<+wWDbSs8YQ!6Vc{D`|=S(gTI4)KC-a zK=Dbqi1PIu^5nu?x=(mS!cMc12=uO^+zXH?>u<VM-bfnC!W?J{->7xOaYa2!W1_8ue7Jbi{&y{U^2bnV)ZG2qi{^xo6oqfwj+=D_)Qk$`-NxUWSG zHwI!&w0R6IFw&8G7&>|p%F6l_7=~&+-Z3Y~fef*LFPbaScIhy*og&=Zb^a!smdrbi zU;2x5Rhxt906xDX$?Dq(*S^G>(y(%aA}wdj8^}(meH4Z>7XPI$6~oweW4uA!6rfco zE8-LlmXlTpi^;*V>EBn=g8GM(6L<*$-4gT_C^dAFg|-b!!9DCa0<|}g<}jFP`8i~EI_X3q zvukof^gh4T-T59cZ#2!*by8+*OQ(FVB?!$r@04ql06DLWEdC3#XsxQ6HP;a6eb%1e zYntO0s1hk-vs(ScFV{Y)%Dg1xmH3!Z2Hr`xIM{|nnw6U|%;u*HOCOR}4G4sPEbuGt zwAwdg!_n?O$Q5L{wdB3}rWpwF#F|aM1n_7M=}ZH6s$iqtmRWoiVa|ZnJ2)yBcMnzD z4sbn`6FO&|0%VVz=!@x72laDOyYa%wJ#?`7o~WmgqJOi9jB3j2TA9{ZUUg+Uh9{-f z75{YMzgb-WPqAMrQA!QrENK7RGc0=14W568hmKm_b=)CZ8H}g#Ze`;4SmBTIM(FDD zn3r?Hts9dO9Ak9wn*{_m-gM-+1*ld^t@ zYYWpB!%u($8T)eS#u>fo5%>L$ITL>6n9rXGvaR(;E;^c#YbpXy14n6b-}ER3=+-1P zAA4rGz1;t_Yn-VN=LdO{$HKuaR)dO2MFGNx4oXF z#`Tl_Gc{RRN+_htKiOP4hV##O$8?|y>p++9FJSzyz6~4-w(#-74))WM7Uoi1N0KBW zOC`fb?z2p!M?XiCAp_vXfeYVwl$8XOyVD4+H2}0CM6Y}3VpSMcx{dG9eS5bMIaRS) z5`Xd$dNFx_% zO2n|>=g{$QKT=D-8C`|#&*aU#Nus4*t{PzZ3`iffobqo-9gLCcr%tWnLBskJcgHvA zNlU%TCA4^oRco%n&qOz{tQm&{dbeLsTQs1!6>!%4r}*lhVi#T>H#=8ydp#L>*`C#` zgwZWz@Fzo`5MYSCulr|%$N6exh|%j73>&ed+oqHPY%1Vq)EB?X6e>|4?|C6>P6k!^wJ9CiyF1{jtc^KqO z;KddpRLkm7vm6@YWg$YIJl^Zzje)84;p~Z}CjY0Rb-8O3MnWgq0p*(|Zd&_&e*ZBG zUx(lDS847BtGtRA5{7D7vbGHA3Ao&WeV_GQ{2Z-S9yGG9=o`9JwFZ=Pyh$lK zZ)gtoLNzOKUlnj5?tQ7!Pyk6Jysmn#`Z!E`!)f5w_ic}?H zMQYRotNFue5Q5z+x0FF-4$lx^;y)L7G*qL>rEw@{gvi&cgOAs;PUSCDOeKJ&rd`lU z!dJ?5up?D|w-@z5OsjbtFDw9Ol(To11jy~++@q|E96HtN!Nj1q(J9B7uOGiBriP^* z-po60qpp^OgC0_^SH8P>A9%NMOsW4)7Jyf|3O;-kp8p$3fyx&T;dpDY(>#OhqnCi7 zNP=#Dc-a0G?_io;znaT8>}yI9jt1T?9>1ebMG;#CI0?W-{zY}`!&FxEP&~^y46G-X6<(mYcdj ztubD{oAEq|hrbmX>sK2r2onP zb*Z&0HU7IV2Br9?Sj(jp9G!IS50A{UrjRNB&ov=oA14DFYPLnIl@EEvWr}kfjhc8{ z`?p5qxD4K=nNUcXaQ3|CIo!1EdPQ8AZZ=i>C;ap6&aa!>^9hLi;k_|951>t_4u)KV zex*$8w_tVq5VBy|p{?Q9l=~=h#bOv!PfjoWgMOws?K3*0>$>UwC3z@RnJy+HaO{ln!T?bRU(yW6eV*c<(14d+6QDy5U%@hQv z8z(11@!0WEMUg&NQYzTxgpEtOUcwTqwD5N2{>2e{sHl1jAZWbobxgDEzTB2|hQXe1 zWjwT)V17pP%}p*O`Hv z6mdDhLgOc?b?=EWfTEvIjnl+Fx-ksaR+@)^Ux&clQCxLh-iVSW1n{mH&tDe%<7dWv z8HGMeqd0HlfVwk&o-JZ!YN~230tyEOV}Lb5rw_)HH(-i7%d$F5R9W6u0#vjk)ub38 zL(dL6D6Mp9Dgy`Kc}{pS3dixj8E?(7*t$A0hh|fo+w$u)^gMP}4CTLOFG+j;i#ec( z1Ys|6Ksw;Pjt_9*&9{hsr}kWjc|HQ0QR$PU1QtF}JV~KYXmf54nN>ySQRdz6I-xPn znDA)w)RM-o$*~-Q?Hru{byd?hi{pyxKCdp^o!vt7rY|p|whcjSw1*&BF6&r|Ec` z!hKLf`@5p(za#XVB!|VZj;ceDEP!z^^v^0o-~P5$+Dp3s&tg!Me~S0gTm|Ylu;bPS zLWC7{vu_)Jd8fY8I--M}$FLOJsUh%qa3)p_4>R1avh`Q8KeJ{$p<+bP&@M8CpKXm4 z7`%L$7ioFi(*O02dJ{vbA_jes$vni7v+!HDuW4R}aCKM9>tjEW4 zQhzugf@m?2o-JH^m8Q-++=A(1qHRR-mDg&2)}2L0j8nJRq6%v1kuhilW`QPfOQR)n zr19vr)gBoSV(SI>zG>5-NoNVY3MQAD5oJGI%BG7$*H0gxzFIJy7y#x(4h0|se6@E_ zYEinrQb%I%q@tH6rxO}@qguG;GGcnOmm=gTj#iuZPsSIgNu;(f4aX2GW=2-!OWC& z%7i`L&|B$1A~439u@BNXT5~Q~kdp~tLE*q;HQ6uzB#A{u7B~k$Y!q5(0c$b0YSK7X zFRMUP}@0)CbDKg9+AS1c-(^Rl1TQyQ>B^m_|Lh5-q?;NqLeVDwXSxDiLa=|#M2~K6@oPMNugB%j0{lHsi!q> zq)X4BE!}$<>96>KW0{|Ud5hk&o2U_+#h#*ts}2SwHwM;-r%8LYn-RJ$K4ii%(Tsem ziVlSPlYo9^`IE=gf@)vSQ9+8xzM&0z#pm@a<)7YlI?q(*&w`8KbgthA-T*BCc{rZA z5wft1;UlJ`3s@^Y)`6mg_R?38V&IY$yh{@heXe6#q&j&qi5WU|Z^m)-hlFPuT{Vl< zT`EPkbk5v>OGVk;*sS5nROb{Em4_;QijyfhS%`{ANoQA0ef#w&)+~XHemhVUiAo)L z1Qv&om8>N6(-S7suMw)_7%uT10re3h!7tkwH^-b5vgmRb$- zEo9YWlW^4+#wZ9B%aVRzs`uYLPZnN&!TChYPv3NHH1182>re#2ZKE>Hd7o`((D<%F zPVQAoFRoHinY4Q6vO{5&F&mMRnph7CuZ%)_Y6^?ZAT1D_)|X}9CZT-a{yPbVeWf9C z=5~6XK(NRNhsn7+&9nSOM#*@E8A`CduQUBUW7VzUzkmW@E)q2T+2GTt)S-T#|2BtH z2eMVkU733IRji`d*W4JMM;{Pk9!HICf~y@~h{?QL-8;9<@2jn}Sl)#Jh5Rf+=GxfN zC~wyDI3<0^7S?CwAz}$GR-*jG9>z^QyGIcvKf9y>Rq)XxqP^zlbyB;(i->B#jkm54 z3yhX2COhA9tNFc=AU#es%|Ip9Y}LE-fPObPa8Tn|9WV>^QqXFr*WsRL3d$kjfN>*H zaz))8zooj92*`(rA}0|1Ly?`-qQQVgQGSpaUC`EwZAk9raI1*%N!1P1{#C^kuVWV+ zGnHaRQB<*VoG>Gxflhl1)C70?Ee|QaTWpFPG=GR;j%odLnRWhCy!}t{+MN=ZIuaiV+jvZ@>Z@;noJm-P%;aCFxn?0+SK6IW2uo45OW(|Q-ShO1n zI9+a4KG6%>SdlZu7%CEWYMU|4{R6B6Bf*8{*}j7=(SWST5ob@&q|0Pu#bmgxFw5475ym9cZ@N%5h8VO` zAyPP!e=B2p@{hd1Js#DNmlZyqoC5IU%wr|RMp#sXS2QPRP5Zz+8c8Mue;`NIGEQyK zWu%Lmq+_Ir+sp=4W+LOj>`IMOb;bPNPgCDs0gno?^qx)${E9uqjHRt{{q2K0)q-WH z;^-n3DEsA59!p(fwh)CQEB=~BQDpJ zRQ*@!v%9>{YYAt$Du>2?i;0!O)84{%e{G^=^zub>E7Vgx4i_nK<>nyVD@;Pq{v{NI zjZylF;bF*J^v-zqx1w5l*%rQ>f_%rGrh#a>WRv}_cd5Z%6@;MBMEk@(1I``nK2e;nyCpG(~4{u@|rYob?lH&@1zWHv2{tGbazju7V z4s-i>0-1bbq^?SP1l7C1@yweK;z+mgcK9$qh7Z8(CU$J%c@?|_{v--$k6f^^0t5jM}w7>sy`co=yGJlJ) zmlpweWYfc_q_V#b=Qch2e9FcW1+SXJSO7Nu?7LnCi%#D6`5YRCQZ5VV`$P}35 z%tw83b=7Bx@eCu#!#X#bjk91tVp0wP{uvaNq;sZyXVOMB-t2TblMg{a7@4G|E?*D9 zq&5)11T&O)-o%|G671< zvi?##fymLLw(wnU2)$$S5%F5dXu2Iv%ULxn1q5-hjEP|mfW}QWXvn<`LxvVeaBp_!l9uHygLTQbZ*}*Otv5~5gWvn#a&d$8Y{ApC;)R1hOf&rq34=b z9>~&dtf&CYfHgn(>xVL!s7;@iTShpOvdck%s{3{*nwHE>t5v7n@is?JH@)&E>Yl^b z^ka?{KalZd`oTxHS#@=+>Xel#Z51U92uIX{Dv|(K^ww1UUnQFIkUq!?D*7P>F2cK=phqV4$5uj=M5ZAk zvff|LT5C7h zZq&Bc0sd2_elS`A06T%^am1sQz9B#4p5<w11K;Mli2%ehpFK((o&tgoZCt|UBAcdHd-PfKgb)*0v6?>> zf-kT#U`wTIrf(?D1-6*sy`%Y$Ks_tH*>9)iQ7fTZw;lGAX-u<1Q(6%S{a7=X;%CK4 zyMXFM#GoC!{8LilW^S;fCjC0w!7s25%-!{xu3E?A-in{!W;bDV40TBnNR=)+*WBTb zQWR~;e6PP$)7iYL2veR+O4a}F?byx zW_OaYJSsqqG9W*Fy&&XLdK9iIO117S1|}xOq~akeU_SbC>NWa-!}foQRsJc~3i>y~ zs%h|>`92y0u9weGC%)xy-7tvNw84yHU0rUG{j3dpMnpE|P2SJhoI(l)0Q6c-+riqth| z-mVdb&ZjDPvvaj?6Hy-bcTnp`_x_v*!k3RrtpABWiiu`{YZURB0=hdBsI*DfDO3eY z^}ndkjmVK~OgH%UaI`>un06e+c}e>Y96-$&u5C`<-dr_-_1JahnZ@(@`wi# zzM6_LdewJ>r6hm0X{t-0O`X+Sqv^@ffF8IQxu+{^oW~mw=-3Q~_H2#5ShO7hR1>{e zGGS~CxFGk5V^F`@ewTfDVyFDZ#_&UlHi7~c`C=IM6{JlV$7c(=OP>WwkZtvyV1$UOVVCb%5)OP~ip}b)JAn2d>zWy=c>`*l&kM_p^ z#mlbLL2{hWtv2bRXROs9^}0ipJ;sbwFG31-<3slny2me$VCy)(3M)K|T4+WqLcqd{ zuV1hr%+nQ*B61_3z>*-SC#|*H7^Mo&&^S^64dmFuQd$-ti`c^6hTiWZbwjvq=;FSz zGIV)ysj2KzEI><#-9xlL-SUrE;SfCRMrp1mz0t1H39*#5#nC>Icjz~zCNmz3y4mDe zN!dpn+??J?7vNJN7)?fbYBs%Pk^=PLP|4g(wQ`0a#|XowSC0RU zRgp7uSgp?DzGN#skPv)IE2uYPzc}XAi(NBUuYL=4v^KecV6N>~R-Hu`mv@!=oC&OL zgnvFdbTxug81&mYP~!a8zQE@np0x!*tu$xO@!trfiTtWyCVcs12$IH#55gqn=$275 zD|t3_MK_9|o>EW3%~)b+y#Ev@{9iFs=3`gzb#R{v;9*#CaI{7#}z|DgCq*gNS?BWHcE~tR;rb!vn8j7s7y$_>6sl=$MY=;IQuUmP)!m*hI~#ON6J#Yg9hqf`=p|rN?-0 zhm&~zj59m|PZTe=-XF;<^(pN9wnTw?f_3>+dhOYkE9SgR7=Y`aIS zvp?u0Vvmo42H-FVF*IH(b5oVv!7gn@+m_7)jtQ9xReql>x}O{!;D2J;sL+Mp|De!9 zpcfV(e6uxl?h)2eaL z)Q{2j=K^vKXog{C!=*Dt$;z4WnzwzKh)g0ZfNJl_&kStkfL&!8AaYHD=T>JMYJNiV=ye?#e%EFnVe z+ve6{blWdvBbjpk+sv|FOWzj2nPJ|Z&p526N_lkRq#k=>#3FlvZR%oXq#KzoU4Ul~ z0YycvMNyTQ^1@|*!%@EB({{WKjdOu@#=R!qY?*p z0BlG9TjrM%rsovOs$*6CHPloJJps0FvnNEL*hD(a3Q;66@^09yQqXKgF7r zovM<*{HF6V4}b#LMU@*46nRm;1dNNM4QDUNI6ZY7URr`%2+QZ zRqUWu2TRjxt6hg$@}!-#OYsz4P)qm}G<@dqRofu6v+}-iu)$+*;z{aR-=FZu=QF8H zxU-xu&g$h<$jWn>K0G<^wqJ=P>(zmNPj}+kj)DR-w27@MF~Mq=ITFJJ%`G4qTYAh1 zr|~xIvQAL9&9o$)Y1^<40fX^26Ro<;zu9M?l^#m-HTg;pLcHD^;d&ij=d zYA5@M5}jnme)%4m&=zMJAf!$)m?~-caU5!NymwKYz6u8$_dGX`vNPT5x0?ZB@B=J* z(72t5Ux(Ih{3x`F(PCLiLzFh;ykMttxs|VIDC_aeB$b2Ua(pBuTLlDGIqHsiPBTy- zQ*qA46Wii3Cz~;+Vg6odM@;Pl>qSo4(R-JNq8)7W$XRU43t(}Zt>b3tCXfmROE4xW z;$LKojD~sqDD3$_7T%p%z_j1{v9e$Zvi{pagiApO3Esl0j}eJnM8*>n|&5>2j!?<}}&~rf%=A$-ENv zyk8<=ok*rzgW+*X)6Z@#7gzRwyIbH8{}g+&p|Oo+nxNRL)WRmBj5D*qV7=2)W`d+d zfNEZdjK_90^)I*@C-U+gsVZ6hv+^{{tHwrthoHl|^YaO~uhQQJNoFvE68lHOzK1&3EeL z#fx(?(+FR7JY0$)oB~-unX5P*Ty$-i*KC-E#_#$^dzJ}G>nWQBp#8iaY^z49$RabR+0IME$(P z_ghsZ!GVkO#I}mu#l?x8Nb(Nh<2H5=Da=Xcn%XSVRfrjaL<|CA|y$;qq zvEu01%Dq_8ZL+az_5R^fvS%`$VdOwb`!0C!L0if!YhPWx09!B@>OQ>!GJej3-o3(( zgwYzyG|FCZ^2um+uoRy9`?w;9(cLNIl!xMfS1mDJg*PxHkw|z11;A^m7VR|kubvN9 zWg6^4cf7-)K-L5*Zw#dFby^zB_5hEAUp6?M!8EJIHcm7z6VtD&d@+Ut`&Ot5P#c5q zY9@CR`+OwZ?;F!i?gNA&Ok^|RiE(sw9Qk}PcCVgr70-y@G(&IXODb%IkT^)yboZ~7 z;o$Ks!wR;QSdeWznEG#^<#;=o;ByD0*1~3`n!p2?VTrjw&jvI0@4!CWekuA7Ax-YW zvxcJ1X^Nh3GQY3>irGA@a5Pfm3@ik6ML?PcxoxN@vRyzwybgErqg$fQ?^`piC@uSf zxUO_FF8~hM<@y^b_#(Ibreq-3^WNyxg|?5JzDL<+X~HFZl@eHqk04jY-@RKo1g-^! z!3ux|kdQ+b)yPJUZqn_>V&-9)e|(Sl-!3M~CJCQ+Rs8kjlTia*=fefsSKX2 zdxO~YiLRfT0U&y*A&uQM{ekEp13rz@!ukj?TJ}2<*K-KT!Q{-{N1%rHxQlU9gFu$&D!?^SQE_wh2jM=ov&E#pjby zY1SGG$_T&$8q2y;l;1J?96wAdbQcllET5Uwn^9EAeIzzeXM5Q#2$_^OGl;Rq+~HpN zMaf`VFDx(u5mjp-isQ)%OL&0oons$E@}qw{uH6|Pt$U1i2S_fVi%;L*_Fi4yjQp!b zIsMKCU6ieSC2!#p8;^(IJ(c-?Q*Bk(G{x<~4m2UvEX6oZnTLr#)2gD8YE6W>KmVvs zg3K#Ca*5efiQn^gNY4-4G+v|iP3y~Xh$18VXmo@mH=2=*fKY1bIqpS<7O&CjK?z`N zRs2S%S}*7H&=`Ys*SW4y4y&$Em3C9cH%~@&1ffv<`&TK9MT$DphXOHr1BU4Y87K6^ zQ(k)$iK`lP&Qd_RrohuDyePb@PF$cyqhQ~&IDnD@1Ops74K7P&i=auf+ZB8pr(vcF zUuqc0yPM<=d=N3uW;>MGFBXl=Ieq){(FDpoahl@gJIN8Z1#n?_ah4Kg%|s$AC5}kZ zLK_Pax4#N#tiB~)_*Pn~ki=K~MPRXuK4m8#)?hNqa5xlFlM;gZU$;^Ruy3%p9H^No z391XLY1lutCJUBLT>^SqBiJyQEF9*YtHJT2dE{3%?GHf?H}EA)$y9Z3lwpl&lOb>O zxoMA{t?<EkwQPYjkTA<2xN?8P7%iVsKvkAHL~R?DT9tnNxBP_|L}w`n^il)(Z%8j*b{pvR zif~c#pu6Y_(xS;zJ?N1coZ6KEkN=*&XiLep#5qWNbG98lH}`F)x7(G#-h$7cT3wq) zzuJ_Qg^PaUyd1ekU1kbnkU~eBl$yGqbUHi$^X*HPIexdt{|JgA9Rh?1TujEqNueL9 z@Yy7ht&&rDLYIhlCs%W0s6qTd3CbXDsuwb-1VhDB+-7n9 z2JE}-c;?j--VJFLg$AjI>LS#(dHt@0h7t<#HlQ01)+T8`B%_OjMqr9;nvC;ZJsq2{ z#aLPgp(}Y^2Ux@sXpiqF?lGt!ABU~a(k7tY^+Hd*o>xSCXIy(7*#kRZNi)j9 zP}T7P8tsuzzg?Fc80?cY@X`a$uW`(SLtGusqfq;-U@MT#yu{N1bQl|oQ?7=4dGJ#?bz*zs&Z^dsU53V`C_2Z5b_dw>PWAR{>UK2!W>w9w2}lw8Pii)`UJ9W zke7r%=8ofdI=Y~o_Hu`lJ1fq^=bg~K^K(*XqhsSp#K0-fU-aKV^m6d*IB-J_6TSWo zF}RacTWF#k2M>QiP0*Drq;~xARFYCJ8hJxLv$j|*PVrZH9QH}Kg+x!J``}eY$89BC zn9V0nCm74vg{`!EFMFdh1nl3rMb97pYpJy`X}q?(-uLDAW?2Gq_X}K%-A-*?Q))*$ zo`EV>{bvGJQD|RDdjujX+i3474us~^0ua9Vtrwi}^4e(Kxvy`9OzsFjZ=zBR*U<`f zHt=>bFvU3LR`+PxnV_^AfyGvfFhrrzGh~v|nzzN*igqRWtoU1P4Pv_y zd+IMf4`fdo6l#nf;na;WOs(&~H#3~AtL9YsoK=31p$ogk%&F$bGg+gJS_#2whbciW zN&i#a{ZFw-9F)-gU#EX9^jnb-3U47KJmTv7f+@_*?o#Bd*dfBVDQBpKPy(X}~ZcDCl@EC*@KdE+V== z1D$Ajnvv3Tp-T!el{%3Qhq%|$y{jcKr`0Rg0tj8dX07l8pL0dO`3Zi-rK$@2j5vNf zz>%DnnB>=wKDgCNB1VMB&HfW3?vq*g5&+Af2PP!<*k+B~&eti6Oq56ZBDrmV&%fJ< z%wOtt#(k&~@b{*E?*zv5604(Bkka~yY@;=>lsUz9yErr?^s(3zR7-JRZEFBEDHQ&MHXPat*GqwvL$^~Yhf zNL{P*>e2debyx{y{9F49_1Oo7l=h5Tlwq^Y8Yf*c)*&(!Dujj7e(i)0%kxg9gP{TY z)1}`#y6Osb*ICn^;QjZ{YB~uBH4Qsz9uyix2p=K#9;9st7{_$oqiXWC((te&XA!Dp zx4UVm^zCV`_@*X00oiQZzz45N*tSQcDY=J1s~#t;Uq^t_c6wJWk`zlM@$mQ8N)IE$ z(`4PMYf1^G&WcC&Oj>-ZnGiBX52OP9RW9#K3%_O8Q)cyxi?3YZg)}GA56&TO7}#BSluI3W2RV6;?Xq*t3Ra;t(DvL{@)g#W zz63h38|gDu>yls*TTmAcuc0Q-^zO?=${Eue?PdM1R^_I&Wu4iU#V620!wTyk`H-xY z(ru|uKio?Ak2Nr~yALYszz5Hrd)B!UoM7V|nnHhi`>#*Yq>qklkg>ClbdJpF3WlT{ z@3SI_(x$c!wL|c*X8_ZLM%|?bTAueysS{xzmSbHp7*iZf1RUb+gTS(+)#=+1eKAbY1q*CXOkU<;)?m0$BZ|l>jymmdDUMwa|5N<-PqF*B1%<6IE{ckh#_xiA|^Ha#KH;UZxu%g}+OMmtMI64RI zz?KDw#x^FlZQHhO+qRudY}>Y-iEZ09Uf%tP-F^C0t=bzQo?LPco8o63Bd(cK*XXKp z9f~}qK3sSd(x`nY8co182{YEbcvD0TflndP|$GI`%mT@&Xf9>3xe zH+1;arq?-Wc&?!P;(AF_iU4@W4^NGeH0N%w&I6JQ=p^a@BoHWNLL!%5Fkj#Xq`Z2% z(KOEZgeca_h+5w`Hu>*J^qfD~=Rc+iK0cWL-V{%r4y?}u1!A8f>e!lNJ_0OPRsmL4 zG5qW$#6gDdl*Rl~lNzLQg(m?4r`2v+fdgd@n7orG{UMEX1YZ|yO{OqqO~$mC6(DW` z?HEfySE}`#h38zgbwaF>{~Z`oP%8Tv1UH_QG!;z&vSofj#%GX%a4p~PWP0M!{a}qt z=Ht;j;Ln|s7%alzq?T*PU`rU0&`1u#VEV;dP?L9YkQ!l+%#8($%L ziV=^^18euqqlH8-yA^H5@uoi}`CST7W$qo1CBiDsKNo+P4T~GeMzZ?YgkYv!+z3`d z0pHsK`C%9rmUs%bM^Gspsn$2dA8jrl(!_R$IO0kV-qy7(`B32gD*%U1Si@zNsMNP= zA33@uWW;YJtfZPsxEHx4o<3YRt|w6`w_GCI$GC^YY3YLtiN~x^@1J6VKwwwvbW($} z227J(`-Ca@8lr-FFP1}jWI-Fw0DM&kqF96OVwtEu-{P3o5!YynKg^g1{=Rwo(J#z^ zHc0^wR@_~_On=xt>rz#*DxgK&BEONE9U zMs+hg>nJ>cCZ_0bxGo|0JZ|Y1t+dLov0&_u5VKI@268?Z{~i@)h@8+`zz4>OHH?4} z^x-3!j+BEoii-nWy75qO_sPR@O#Xu-=cNfNwEg!@ey(vCLBSPXP=-~Lc> zL@Nzaj}tq}4W!!HO=2PLv=jV0m^86KZDTwxzhWOtnL^qOl~#T?-#)(Am8e#2Dyot? z0=>RBkv{s+9s40pN7dWo4{p;FdvLhKsv%U%d!8C@3`d;4s;jSja(&LH^IOcWcz|F0 zi<1*SJPm%v*`UV1$H{&`KI}?ob_jPHx^KZ0$2InczSIPwT1v{s$*Ay*_1=Udof%mY zh$a1}q!SF@W*Sq^3+}nZ#iMEF`?9tn)6TuitMxpg2)~is)W65t-;(wJ6hkooQoMfV zLs~_2nUei{-;E~2?%a<6#;m^~VrhO~h9N$_Ei6TvIP3~Wo%&;1=G z<)E2Mr@u_P%EyTDv=5Or6fX&0)X1Gu#{5+-3_)S*IE1pFj z%|SSFFAB|_Jbnd|i`kwFO~#L$@@Rf|d-kG1+UC6Glfa?r*n)a&{@%)h)MP+LeggMe zO}JN}e+k_ws>f+zz%^siAk?Rx>|V2omEPoQb|5~%+lPxLaPKIb9DgifD)xf_kt_nFlyiV*$7Ow3dG8|c*AVBf3LsBey`70PV$@~LDEZNBt!2D)Hb%6(Y*s6esF^WNZ9w;WTS+b2 z@=lk{9@H4vMC`A>eCCfpdk1j80tLx*-~?WUT9GjFjSVPtJiz}V>e!X65EJ$aQk^h0O~zQh+Oj=h0f=ICab zSM0=Z3Z$7<8?2#fR6YS4$!xJ{7Ij^A47EtS9;{zfDDHt>jRaex*dW~*frswz16p=v zqMf8bG~;A2kto;#zF%y2jG~(}zYGGY>L4Jk;kbhpBd#;8lDk&OM|Q(rL-x39>ZFP~ zq-5BnYgUggIyeuh7qREh&_5lo)Uh?`W0sW1uttBVY*(>WfzKu^LV%vSZHBsZ~zy`Zs*F`#JZaWz|c)5{ljK-1{4 z^jVch$pi4Os)ihalsrUWy1o_PFM?VkJ$i<#3vMr-Qi$`AS_}iX*4|2A+Og?Sa+cY} zZ-rsKZ%^kdy$879JIHB9Vlne;*JNcBLscs4`>K#%n1wy2KsllslncGerI$xBa@;C1 z3&Xk!lz_fp5JVKW9&khs2s*9X_$b7uH;tNau_d%7kbCPoQd z9z=%i3UBmRuo%u`*{|d2Ey#BWRh|`T;GQRGKxJ`uMb4c|fSISNt8y>?9F*p@WFGlQ z!hq??tc-~djwjzs#6x@T#f)ba8&!8d-)W@N9zeBk-in~Dg<1dHPFw~GtwoSB=z}ye z2ro#I52^q|%D+OW?4RHvNYt4>WVE3a^{jeE2IG|^mop$9>$-d1PjU(ZNQNV&gW5nV zGGA}xc_(C5(_fwt5V>qB-5YCuNtRt)>7MBJ^6~J+SUBOAG|P@Wgie-~ti*SOwM~8i zMiI{QY=2l5=~Kr1V3*i9{;QBkeFylSm%exBnnN6>P3W3?GDDqTbJ23;^yxmaxNuPg zi7i7FsC0P`EL1~mo_PYEPzAbl#bUt>BLW>+_G16%;kOX(ktp%j-X<>S0n@w|f~d_= z99xKeebjSt`6(l;Y0+X;JAxM7wxcdB$(BE*&yvVdJIU+c#9C8ddQ3a(_xDPt5~Bo6 zTRJ_~jYnx}_EHDut^(%Qagw38o@(?Qd|}6&2d`VFrXGimcfYU?&BW0UhsuNXT#O|(D3q;DVy~0x&bwE*kKvV>zXyX)j zN|QX}WSFYS+Pz}8_a!SB>dBb}7k@iJ^q)eC&{zv2hUcKxla&dbNxUgph*eC;(3kTz z%y&7Lf6tq4bVoEfQ^b})n>E1Do%Dx-lz`DR**n!VeP=$NQ;0W zr@;hk=+ou1{#E|?j*vB*IEqQVgHmzD zRS)>KNz9Iu@rVre77+^_YCkU|CXA6f<5p?4R#IvRhh)n!b0C|gj#q1{^!d?E79=-p zfy0eOqIT&^5~gX<33*s2{Q;%&ZWt!oqDQ}p^i6Kp$TQ@SI0OiIB;EJN2~`yFTS3iY z*jM@j+g6mkd76BI2h_9ilQx3=i*S?BF|Tzso*vzb>EI$s)l$%ku@;8a4KNM;-1xN{ zawfV5$J9@`Rtk<7Vh?(vs&C@aM(rK?X}HvGUiXyqS~$%`3RJBER+(~I#_ti8csHov zerBT(0gs6{bgIvuMDyFUxm$vpo5CW?QYQw<3>K1?;cee4wKh$^apal6v|}WA@QIY} zAm{bqjfeKfO9u#LVLXI$zwe|7w~qHr*^yV%{^>cDSpa)fJ4aJHh~IT4>)i+mU~i4Q zp_mvQu>25}JVOdYEV_AID{OlBG2Uj`DR1=p4L9u|ecj$RbQGWGle~Fvdydg%^ffU4KDC<-AJE0mh`kOxfBXaPpx4{cd_pl* zy7YGUT9snKc-ky+dUs!qH@oug{u3FIDufIC1{C8bjf)8mT(!`_fSoCpUaAGM~#RbAj@JR4dKr;<^9JQP+Oolt_!JCW#tGeF*?<<2anA96%7#+$ z8(=VH<&_rNH9)svcX+M~{<2ytFGeQcP{V zZMw{8lNdq~FoYE7n8lRZBUpw!k@T4DDG++q@`DMD1jf*vc}YVP-j+kAE0|1U#mtSM z^@i7hmZ$L&_vDYx8)qLp*HNUNbj7crMT}NQ*z0RL3Y{j;)XNw-LbC=O~cx) zd8NUHI64quQ&}9W#_}W>L^)9=Z?s;wNzeMpuUMx}oxA5`w!|~7xrc(OWP$*j^Z>E+ z0#)RvC<#^AQ7SNNfLgVH8D}`{RG2Qd5I~Z=tTm6obH1^FX*hPBlP){w7aAMBS0(B| zblrYo@LLeDWi1q)^)VMHNd>)HT{KEaM5_X&hhQ(O9qTlQQ8b>Q{By--w-?V3AVV^N z9OXR`#xhQ+U2i}Xm)EhNvPkq9nyAm&duK+f_T%lr)*9ZV@ve8P>468i;MM6bM%<8u ziEeVJi$w!P;(uxS5!awjbC9ObNRJP)J6;{=jmEWqLM`saJWQ4x2s{tj5jN^ncPhOX zU;seG(Y3E6R7g^0V<8)j#hv#AfKJK!3XOB$h>seudXCR`Lc&jiR}N?s(ch<)@Br-K zW0t+akd4l?E_7@s^AqGflry`f=?B(@USgJw6mR64BzT+Gb9RV&pf;%@35D-xAGMA0 zyHd@7*hNLBI5D{&Gvck7m$jS*MYDVw6V)m9r8nYY;^Y|fjKK=CEp1a=z|qpkOq)tC z5(DP|WQJ@H7FB=ssx@XkG<8jkd%{`z+rMs=E)3x~oPy3*wKD1~B%0K>XFd;rW>kFF zorINcF3k16>lx%-z9MOf(-!7dTZd$m)1aAqMTnG~lWE~4Yv;P-9EJ%70)IXO>V7<#lv` zw8E6ivXLcYy2c|@cql*y(f_ffL;K{SH;DQ#+h|p@DU{*7AO<6%GG~`iJ?X04EXW6M z`5&k&D$o1bFI3qTyuEUAUh%f4Fv?(^Ra^EmOkb{x#dA8)!WrCQZooc(J3>SMpJGVj zUy6U#5nSbmqHP26VIiH$`anmFabjJk>eSX(W0ets@%xruSJtB6?Ip(V?cFGPoG-UwSA0 zyh;;iVzz!Y+#s&>V%zz;D!)^8^@$sQ(IVLI=E!z$=zGW=#&50?F2BA%AQkb*qbuf! zuboP7Pks5tZ9^h$>!O#R`sx{#N`Viz6Vo_ylQgEGPL!AnQk8P5rkA|JIeIA0t>g3@ zZ8XSSg?RWq0v_wSQtUyoQtQD+P_fYV>$po?i*zmJr*Fxfm1{3~E6=l~iPgF#NAPTM z20fD~SQAY4)p;!T$!L2+ua07{QSoQYDkPZSdJBk#uQc;NDQ5iPbX0T2j6Uc^nCxk| zR~e^>I*3}A3UsA9T`nEkC_!UT=e+}YgX&00dbGZB$3v&EkG^BaxP<76HkgnU}MLMLR%ZVD9fH(zQL@CRRVGBA&+Uldq za_Z?45&!Nx5dF(Qq3~VEMx&)hg>5!88?p;~tw&^~V;_}0!9DLRFg3erOZoW}LTdSK z51|aymoPFmkmO_(j9}5>u2Ikk@NE-@_Le=pa~CF5MYv3Bq+|?m%{;^vcTG2w&7jXR z24)$uz1$!q`0BPmP^RpZ>Wem1t)-Z$q@nAfUVtQdaL4$0F81ZJVnMR+OQic_N^l># zG(Jv;TqNDxN~ZO$?H8750<>=PO%vg=g;kh}k zE46DXw9D!+sU~5?hi-xFb@KJ z!?=v}!MGjC^c=)XbnC{g-cOgpM*a#C*^L>|rc({y@TiG>n>@4xrFid8KMTnCqEvxJ za00irv<x|eduvk8(Qe@ z9Uvtp=WbO0Od;c^l|x#@U~=PAI0$%sm6jO0hk6^%y+E_Uv>3A@CF#VaNy6T_C+do3 zJKr}X?+lG~;-&+~osbRYDL1ZM@f?$!^tgRqkWaH6Ats66n>6#iS-cnz=P_#z-$Ul> zuFuVP!fV!>Q1$BKVTA(Twc?U`tx1$bGq+jEk^oWzr4c9T%Z2E$t8ak9^ElZxwFXKy z7p?*p?cbbx%9$rJ9S}L24yGKfDgkfznS@hMNvUNS7#s5Cdv2gL&Ds)3vi1B1mcah?OOSzr6{%+M*c4z!xXzevH`E}wGqzgn zB%8n6rQNXj>F}ABzsl~>+3*WyU-WCDG3VXx7_<`tD==RN=|@hK5@bv4&8!*Fq*}Ec z?2mvbHhq`5f*PR2!s_GTMLq-M_XK8(*{*@e8nZ}V=WSb;AR7>ADvz# z%u?>mOwknh2cau;NxVO{>_~bZBqS_d02*xjpTjZymjkgC(aH9q9y=t)8t1fNRA}pa z`dwCO4@=x&JZPxD6LC(=w@BYQaWDoC$4V#h^e`hky9Z?~k)A&@#8dE1?=#!inKCKhT@jj>osOLg_@#Jg zVbl+LP#W^6E)uS($sS!MArjKZn6Uy;@?uDaSj6ScqJ^EV*&3(o&!HI#2)ThMLwj`@ z+s7r7GHYAshOa@6#Tl%M&(B$fSD$d|^B@N7$@|BZOc66eyv*Po&Yjvop$7SDNx^Co zxJ$6dRS9w9bB2$BLMERw0@cm{q-xgse((@bTlX~FiVOM zbmj62P8uZ6-&#l8p9`@s0H^(+_WTc0>{7zqCOas5$p3VU+Uz0=uT(E@`)O!b1vCEp zqK3@+rC19(7LA4-om>8Tlf&B!l1g69zi=0Mv0~{BAxwrAU;L&aK7(q8eaPqBvkjAyGrdZt?X-WK{i_^6$O z7+6G$T3Cnrv*3vxXy2aAqpm z3nPrcaR!W0c?XSM_$v|9tK*%D59vetTe=*`-_)Hy= zn*8Ut?DI%P?)E1T3q9sWy&c=%N%jt!OZCCC+geGA;K@M(1Nebng8zJC<<3*c%Mdsx zu{9^Tjj+*ST3Vq5r+1ejW5llnr7)d=9swh2+((Gin_S1AP^7-SyYOQmF<@Kwt{zYQ z0~GyvPmPBDTkMuZ({qI7$x_cr>(%`6M|9J7y~%@2%$d>NTJ=iC-1i`szu|z)N(L)6 z092Q~&L!_i-cN34ZF4l#5u_g%f(-t9;5CG;zn+Bv5ls0m^T9mB1nVWw=34xMH9VeZ zFvWj)@Dk>d(_5C1-O^OuievTNkLB-e5pp`UCgUi zvd<`c19ZlzUXO*a{Q+}LmYWT0bl=?x);Q9E)mrs zdFs2t4aq_lX_*hO6~-P<1s0!Wok}-CefELmi_bRB-gjxv9{Cgf6HC*zL@fQMM~4-S zm?W%lIrWHsZdy51SzTn_X1|{yWq(-KxGhcIe|PcfFU7h7A#NBJ!r+2+(8S)|o)-no zWAE{yqZ@loXGW6sv6NCgpS$*qIzEHR_8g!TRl^+H1Y_L%-UjxCY0k{^qcnA9t{_Aa zKDGJrhS=e;gBVlAdOdq4kGPVz2orw{>=D$+MO8-kV#)&nFQ~lk7;~Aj)2bMqYjM>R z##h3764|v|vU}IbAuWdfYQ#3E$}_=J2Ll#-B1at>zDw+|z1e;*J71If(@AytKfO zxEjoD%|AD-I}U3mLl;W!m%$86jSZ39On;Im29Pe+W*h{f>^mZ`e9=&3?!ollyb&`- z5YGBojxz1Arg0$$Kg-wu`mE@_{>CHI`u+#uaP3HrD8C)>2k`VHvw0{Gsqzorujxvu zy^wRM|3ukGYd7ETa;Yz|xtEFHg+H%SU0f3jFTDI}A%HkeEhTKpv#y7TZ zzo^8Pe=?<*;$l8UBu>5yWFH*ZyPXSKac8xV^TTyO#XTtWyt1qlF_q2CWh*P@K5^@; zl@rXzPKK_SdQc??eHaU+XgORkYa>dS*_#ZLc@U#xbTwV{MIaAqJf|#{T6pK7^C^Xm zNi&ddlj2qIz(uMX#g>rCCOe%}VFf8CR9Lh0APckiSy30C4^pV16%|&Sn}$KzZTAbP zzsMDni9wiU9oPF3v_5cQWEG?=@-s(AdBY1XT{y|Q8E2T!Fy#hMpGX8%v@XIKRzu3t z2dS!Xuj|fem{!~dC{SpxIJ4!Ol}<2 ztNbE)5H)rUoY8Y%P0<{3!R(Kbq0IUJ=j#+Tx}W5I`R)rxD5u!mtpUhE9D(S4sFew@(NXfE z;{2W~!`8Lxu|qri{p)sgqU21w1! z5kTcm{GVbdlwXQ9jt(vME6+Fccp0RCWAdnp5zjxr=CKv;k_rtKu`g?K!R3qN4}{=| zHF!EdPA-!Q_6CcIm2}2r=U96(1eFxGL9CWA)b8Xpq#A(7{~=p`(31=0Pc|7tNA#_@ zBo!;kQ50#qh)lI`)}*U_=|~WqXAO|FFXqnr!ZH-#hQqr^nwWZ@rMFl10%+EDX{DcE z_N9dJC8!Q3sI;<>L2-nMXKDeyo^{fXp21GkA&=P}_NB`ayC)>>bw1pyIIs5t>BL6W zBPN%`NcmZmH=)0g8)o$$uN!JcgGRZU6`-QjwKr>NzDQ6=d$}M;m_z*>hH_<|!MVT@ z2nL(5)4PgJguvexKyKXfs7>WGkGd|ZivxKO;_|`E=r)N9d8HadApVh8OnPkM^k%ZT6z&d;pK?QkNqqLY<$@$Y$%ptD3L4zFrKOzx zl9C)M8r>fDla7mBn7Eurb7yM#iy{wU9gJkoAwgUxz#L?_?R+WH800ObdEdX;Utsrl z5k5rjfp!@Wz`LJqD4yC%HirbCbU)|s3h5em583eih^d#-%?XCUpvY~p5M?Bv`OO!l zRe5QD0i|2Donw|fTQQ+l;Z|`xb|1O`a)gnj%*w8gCcqr5 z{O;a9M0ErBkQ@B0uZTy?1#jt-+Dz+IwGp1@1`?}aC7zF{*y9tW!@8QA{YPdXz-Nk( zwEa(7K7b~m1Z}a)RIU?0*v?Bx-Vg#<7mf!GgA}+Qgy(rMM7@ri1yOPGzv4)nzFsYd10qM5m zw9G0g`A7d&Az>E)0Dz1FzUMvAIGFu|2sV*Thf<67v<%4E1@+am4gvTR(W^hFnED0r z26&|@Nl9{ejkB9d$P|+@PIz?XdhE_0w7cc4LJ%oN*A3_Wdv|Ek_NaI?g_X*JQ+-@e8CZnX@~nW@KC-j{W9UHg*!}QQ@rn#iD&3UTI}Dy|^?c5~Jv1mP zy;45;6QJ(yAM?T5KvWhL|A^v8?^_WraSCST+AyRYKbJPT^#yrWEF+&5pITI5`iL*i zaKHdt@enQ|M6!lR>{b>qAEmPK1oyojtbKQ-#J~4TD)*gmMwPQ0%2Ujw3Y zpusDT7uC1CsQrn2Vc;hju93<~`<-&(?ko18_{Vh4cO#X$UAe5yMDY$Fu?A%(Vpm9d z26a}*_soOgUZX|MF9TZKN4;abtcC2ludVSLP&jRouP(=F>sdgry^mPb=K|Zqwi%N^ z|Gnv2wpEvEa7jcyq))NdOB6>+|29=(qa>C>+=A6j66!yn06ffPa4iiR>~6qBO0-Nim_CKj@)7dXRuZYi z=uGl z?nSU;JE__Xjr@?Lqf-t|+v5>ow1COmVp55XV!UCusS9Zl?hYO@!MeOkNUdp}Z77^z z$kQ_c-Kfwo{YP>1FU6V+KcJt7PxCGt){rdU;N68hWt69(j|VW+pF?Lz#zQ&0BJ&`Q z?*n_0sQ#ANH3K~skMS93QE%=t4WM{fY*Au}gpQMhEm+5%PsncrX+UXL^<#ST1H95= zNiy$L#ZKjan?$nq9cVJi9K5>Oxc739kaWv1ORlGndQe;5#Ck)^a`SsZzB(G{uz#Wz z&x#n;N(Jj3DfO$}{MqqB|HZRCAWG(bma*oOm1~MVY6zE`Vpjj@`=D{`vJ(g|ch4%2 zf4o)y09ZUr9ZxCZ)VYwY^Q(cQh;}wJTg)o5OsYKy*m{qDE=eXwG)^rZ56)X#6@``o z!2xY4!t^?%*xQnx`nY9D2Lqp2Mw@$z+%N#qvbf-8vb zMz*ETd+DG0D_$U!TsmUuV|h6bK`$mQjJV zvkZG7?! z`<;1Evvt6NN>i?#-!_!Fd;2Cc!|LzjD3-W{$J% zFD-0KMmQryW_M66F{qnUIN;mQrEfnDFJ?IS{I)$HNK^BaF73t!$4T6v*~C^I;Xv5u zU9=doH?-CHG_+jd$+aGh$&R5YXQ6qBXq+r*Yp-3)FW<<~{u5bns2SbPp;U>xyANm) z74K84Ff=URB#HrQeq8=H?q{l158 z-Jo(7GZ3|lh);ks&F#u2PVG1AckxxWEp52r9k2;caj2n{^!oUxnuwsb_4a}s3yJ|c zkz0@ykmD<+#+OB9rop@Og zqt15`26M(wIR&EDGzUBcUjo}Ft-Skjf60vo`bb?pd^?ENK<-G1HSs`9MBq-e6@((k z_3erlkOp%=Gs%byr$O?T_3?>XF~?cpV)A5Mb@gbXfU#An%3t0AGir^5k)`gSQK)We zU`a8o-lFyodb|82wJx@A=lh5@P;n8jTb2+ze_iA47rjwbst6hq;k;Z=-m~$~isP1! z9-PjoROq=mphiW-nZw&4+Ja9R6t5hr-A(juEnTG7N-DAIK(%7DD8GvwXke6%7Glf% zi3|%~*}i=Y_k9{KTUPGKfE#u$z%U+o9&PDZ>b)B$ajBtWy}g`F^yMqzOVCYg3`}Ml zLc^yvth%}B#m*P+g3Y}YHF}i*n0Bp*;5J`FGhK4s)-rp(Gddeh;L{!p@oz6o^4U4LtEadJGGpj*p7tDyr^ZzeZS{jejOjZsds&KZ z3USxD9F2+cLY9Pwub%QVW^rxz1Fr~TC`-FFn7Ik_rNH?H{8y^XQW&|W+2g7DN3iqV z7uho&8-Km>L3jPKPaqkP-Nmmz42et?Jn;JXAb+=`^}?srL0-3EzN~$al{7V_v=;oP z`ZX>m-$55s;5ec?;r7TBJC9^cFk|8H-kGM3{*+`hD!4y3Br#9VA%l+AW5d>@h|@TA zIQLa(<%)@Wjj5om6{80&2>N*n*%%|h`CU^i(X8R+T%FBJxyX)B9i~=324p)yFcC1i zckaRyOJ^9{(dT9y2aOx)9kbBTVWC2l5VbVdyu!XOZw4MtEks&kjT{ry8ZJR4K?kor z^gt<6O)S;+iaY==&eSCe(|0s}>$uiYTMO2O1qQD zbWkm929FYrB>X9MbRxNAedfVv;$MVjRGhN;r>LVS+$iA8lkY;0q+Haltv;5lD4Gc# zP@ugVK<4!ZUfld;+W8)g$5cVpasCiE9V=9((`OA7LkKef-F20`t@5hd6wBdcq~6Qm z0?1}r2!(sL#jKueGAEgC35_m$N*m+_H6Y@w2sh#NNhGx(+I~%YSma=&HkA>;si)AGI!PiyfY zRPz^rZ~z(!fJpwUWfHE^HRn1n9bOW}UjQ@*?0C7!yN_^%h{u8!&1=8}cs^4lpI6M_ zt(9YRRg|qf-G1FF&`>~Z5&;H!1-eF4jGo=&uE;24UMGhky=t}V3fZRF<`v&=vRd7FA5yM+)Bx9SZ9s}A1d8> zF|F*#p*`0dd74d3UAoIzS|9+plQBgn(GRla^d*rolSY zrLliDPEX|BI!Hr6UXfa%8}s8vzcAeMd4LtL4(j}WUrhW!r?HIS47N08|L8NW*MkII zkgpv|M^M%n$wHXIpU|$!zszU(Vy)Dl`8NfWz`vSQA|bMn9qFW|D;0{Rz`6k#>aM+a zqsdSLsfnoviZo6qu^#Eu8`EIX1WeTqFeQO9y9EWBJgxU)T(~J&35a@64~QDgQg{D} zd_}nJ{A9_5{2`Al4_PcQgk!^9{W;-21T@RPj{HO>-r3W5USSPn2v=@`FV4RS8RO)U z4Q@|0VA6s1`?A>%&!syVPsh`D!C#ee$!x`N6Z{>{zl>raqV93ikzmCc)7&1`dNU3J zijry?)lrX5)wO;$eIyC_SFRrSv0*%@Z_IObK!IRr$oF>qeUgV#UHTd_MBswjrEih$ z{yGWv4-dI0s=DwYwq}Rc{%Buc4+S9J7(yAE57&vV%@exA2DyPej68bbcUH+R-?urB z_5jgmUQS@0^hyzWP(KD_Yut%55>2&4M z?komNPG~#HaM8IC*0taZg`3g+cA`pHpo#$+O&8_#g`O}S>LJzq%zqT~|5ALoZ`+0X z?2a)-CmhFnr)m@$y;?B>mFB`Pi^Ow9(jO}dTz!SNxZZdYww1UqQ?r*=1cCpy##Onu zp!W)mUyp${;vf7IsE^3er^JKTH?C!1R6p43K_4y*`@Wl3#rYc| z)7ywhVmqu6UuU(VGPKTt8$^mrA+phy@6}_^UX*%O?^okZ9=% z)_H$yt?gtSM7Gv9ScC8&SRa;KAE2ig3X%GgjZau`*r*gyt~Do-LM_PK&u9$Q!B34H zF#v5SyS^PT-Py?EJ0y9WLy&hMFWtYZJtmv)objGAUHb`}{m1m~B1}}@b;f^X^pg2Y zEhN8I z$UbfRkzD^74q*;yF-AX-yGpGs&>DAZ9yCTr|NHL5um48KmhmyeD!3>ewC60^a_v8tHdlXae)8-*(gG`j6W`0fOd?MxDV803>r)SEHcTmeb4XV^2 zO%hSDOWd75Y=l9f)Dl1yQdw)cQY9^IIlDyhPXolX7Z2_ zmCeNn?76Z0^N*!oTKdeVi-_EPJ6|kFr1M+R6XN4ra&c)SA!S( z6MuVyjcddDn^#93{8GgLDYP6$CP6Jx5Xi(j$#&(%z0r+`yH+zVww$-fUDrwgEB~%k zO`j_{=I|?sd*8Z9xQjxd!xwx=rwC! zjDqa98)V$00_vav513^g?+i*`JQvT>BvTjEfU`L0rI6w>EVY^N8UDU6(W zpvf;?uNm{IpI0C$Hz&BMmgda6W(n!UZo?r{a;M(5?|5ed!Ek@faWZr;S=TS|2u2BS zvdV0@l-BN0bPxq0i$6q5idCs`369$|r$ivYLCN2ChGeA8qYpwcotver#d(X%= zuW4dlAkUR;X4SU09B-uQBK_($7|V7PQ&&Z$q`&^qsaaVl9?$RpK!u66aF^bCy=S(d z|AzZYN=tng`XcoaG9ysbx%hhq=dSS}Nw{S;b^;X;#gx#~HISQK^f~Ffu

DBpQ~y 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

635O%3QqtQ*?e0S92X%T|EX$> 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 HNrko$f6nov)=~QbCuhA@$&%y?j(Iym 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%0D`EIm?JrfmsD-S?j9E?GmsuvJF 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
^^_3t+(@aX#IS)CY9ak`wV&N;W<@7C2BsHtC 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%0A@=Lf0fz^%T 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 bLno{eowyuRyK9ZRi?e|Mw}$tD0r7#?idCX= literal 0 HcmV?d00001 diff --git a/src/test/separateAudioAI.test.js b/src/test/separateAudioAI.test.js new file mode 100644 index 0000000..7f02265 --- /dev/null +++ b/src/test/separateAudioAI.test.js @@ -0,0 +1,96 @@ +const fs = require('fs'); +const path = require('path'); + +async function testRealUploadFlow() { + // Configuration + const appointmentId = "dbd33650-7a88-4988-94ec-0c38e4a9ab07"; + const backendBaseUrl = "http://localhost:3000/appointments/ai"; + + /** + * Helper function to execute the full upload lifecycle for a single file + */ + async function uploadAudioTrack(fileType, fileName) { + const filePath = path.join(__dirname, fileName); + + // 1. Verify the file exists locally + if (!fs.existsSync(filePath)) { + console.error(`❌ Error: Cannot find ${fileName}. Make sure it is in the same directory as this script.`); + return null; + } + + console.log(`\n🚀 Processing [${fileType.toUpperCase()}] audio track...`); + + // 2. Ask backend for the Presigned URL + console.log(` ➡️ Requesting Presigned URL from Backend...`); + const urlResponse = await fetch(`${backendBaseUrl}/${appointmentId}/upload-url?userType=${fileType}`); + + if (!urlResponse.ok) { + throw new Error(`Backend failed to generate URL: ${await urlResponse.text()}`); + } + + const data = await urlResponse.json(); + const { uploadUrl, objectKey } = data.data; + + // 3. Read the physical file into a Buffer + const fileBuffer = fs.readFileSync(filePath); + + // 4. Upload the Buffer directly to Backblaze B2 + console.log(` ➡️ Uploading ${fileBuffer.byteLength} bytes directly to Backblaze B2...`); + const b2Response = await fetch(uploadUrl, { + method: 'PUT', + headers: { + 'Content-Type': 'audio/webm' // Must strictly match the backend's PutObjectCommand + }, + body: fileBuffer + }); + + if (!b2Response.ok) { + throw new Error(`B2 Upload failed with status ${b2Response.status}: ${await b2Response.text()}`); + } + + console.log(` ✅ Success! File uploaded to B2 at key: ${objectKey}`); + return objectKey; + } + + try { + console.log("=== STARTING CLOUD UPLOAD TEST ==="); + + // Upload both tracks sequentially + const doctorKey = await uploadAudioTrack('DOCTOR', 'doctor.webm'); + const patientKey = await uploadAudioTrack('PATIENT', 'patient.webm'); + + if (!doctorKey || !patientKey) { + console.log("\n⚠️ Aborting test: One or both audio files are missing."); + return; + } + + // --- STEP 5: WAKE UP THE AI --- + console.log(`\n🧠 All files uploaded. Triggering the AI Pipeline...`); + const aiPayload = { + doctorKey: doctorKey, + patientKey: patientKey + }; + + const aiResponse = await fetch(`${backendBaseUrl}/${appointmentId}/process-audio-ai`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(aiPayload) + }); + + if (aiResponse.status === 202) { + console.log(`🎉 SUCCESS! Backend returned 202 Accepted.`); + console.log(`👀 Check your Express server terminal now! You should see the files downloading from B2, transcribing via Whisper, and merging.`); + } else { + console.error(`❌ AI Trigger failed. Status: ${aiResponse.status}`); + console.error(await aiResponse.text()); + } + + } catch (error) { + console.error("\n💥 Test script crashed:", error); + } +} + +// Execute the test +testRealUploadFlow(); \ No newline at end of file diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 8a47148..fd64196 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -347,11 +347,21 @@ export const ErrorMessages = { en: "Provide either new scheduled time or shift minutes", ar: "يرجى تقديم وقت موعد جديد أو عدد دقائق التغيير" }, - + // Agora Errors AGORA_CREDENTIALS_NOT_CONFIGURED: { en: "Agora credentials are not configured", ar: "بيانات اعتماد Agora غير مكونة" + }, + + // AI Errors + INVALID_USER_TYPE: { + en: "Invalid user type. Must be 'doctor' or 'patient'", + ar: "نوع المستخدم غير صالح. يجب أن يكون 'doctor' أو 'patient'" + }, + MISSING_AUDIO_KEYS: { + en: "Missing audio keys. Provide either doctorKey and patientKey, or mixedKey", + ar: "مفاتيح الصوت مفقودة. يرجى تقديم إما doctorKey و patientKey، أو mixedKey" } }; diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 07f13be..55c2e12 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -327,6 +327,12 @@ export const SuccessResponseMessages = { message_ar: "تم إنشاء رابط التحميل لـ BackBlaze B2 بنجاح.", }, + // AI Appointments success messages + AI_PROCESSING_STARTED: { + message_en: "AI processing started successfully.", + message_ar: "تم بدء المعالجة بالذكاء الاصطناعي بنجاح.", + }, + } interface MultiLangMessageObj { diff --git a/tsconfig.json b/tsconfig.json index b216a43..2361a19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,6 @@ "@validators/*": ["validators/*"] } }, - "include": ["src/**/*.ts", "src/**/*.json", ".env", "src/test/backblaze.test.js"], + "include": ["src/**/*.ts", "src/**/*.json", ".env", "src/test/separateAudioAI.test.js"], "exclude": ["node_modules", "src/http", "src/logs"] } From 0802be95c230893e95c3c79c8b8b84f362a6f184 Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 6 Mar 2026 02:51:55 +0200 Subject: [PATCH 182/210] added swagger for ai endpoints --- src/controllers/ai_appointments.controller.ts | 2 +- src/routes/ai_appointments.route.ts | 101 ++++++- src/server.ts | 3 +- src/swagger-output.json | 249 ++++++++++++++++++ src/swagger.mjs | 4 +- src/utils/responseMessages.ts | 4 + 6 files changed, 358 insertions(+), 5 deletions(-) diff --git a/src/controllers/ai_appointments.controller.ts b/src/controllers/ai_appointments.controller.ts index c520823..2532e45 100644 --- a/src/controllers/ai_appointments.controller.ts +++ b/src/controllers/ai_appointments.controller.ts @@ -62,7 +62,7 @@ export class AiAppointmentsController { } const SOAP = this.aiAppointmentsService.generateSOAP(finalScript); - const responseMessage = createMultiLangMessage(SuccessResponseMessages.AI_PROCESSING_STARTED); + const responseMessage = createMultiLangMessage(SuccessResponseMessages.SOAP_GENERATED); res.status(202).json({ ...responseMessage, data: { diff --git a/src/routes/ai_appointments.route.ts b/src/routes/ai_appointments.route.ts index 80d6580..78cd108 100644 --- a/src/routes/ai_appointments.route.ts +++ b/src/routes/ai_appointments.route.ts @@ -14,12 +14,109 @@ export class AiAppointmentsRoute implements Routes { private initializeRoutes(): void { this.router.get(`${this.path}/:appointmentId/upload-url`, - // AuthMiddleware, + /* + #swagger.path = '/{appointmentId}/upload-url' + #swagger.method = 'get' + #swagger.tags = ['AI Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Get a pre-signed upload URL for uploading audio files to S3' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'The ID of the appointment', + required: true, + type: 'string' + } + #swagger.parameters['userType'] = { + in: 'query', + description: 'Type of user recording the audio (DOCTOR, PATIENT, or MIXED)', + required: true, + type: 'string', + enum: ['DOCTOR', 'PATIENT', 'MIXED'] + } + #swagger.responses[200] = { + description: 'Upload URL generated successfully', + schema: { + message: 'Upload URL generated successfully', + messageAr: 'تم إنشاء رابط التحميل بنجاح', + data: { + uploadUrl: 'string', + objectKey: 'string' + } + } + } + #swagger.responses[400] = { + description: 'Bad request - invalid userType parameter' + } + #swagger.responses[401] = { + description: 'Unauthorized - missing or invalid token' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, this.aiAppointmentsController.getUploadUrl ) this.router.post(`${this.path}/:appointmentId/process-audio-ai`, - // AuthMiddleware, + /* + #swagger.path = '/{appointmentId}/process-audio-ai' + #swagger.method = 'post' + #swagger.tags = ['AI Appointments'] + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.description = 'Process audio recordings using AI to generate SOAP notes. Accepts either separate doctor/patient audio keys or a single mixed audio key' + #swagger.parameters['appointmentId'] = { + in: 'path', + description: 'The ID of the appointment', + required: true, + type: 'string' + } + #swagger.parameters['body'] = { + in: 'body', + description: 'Audio file keys for processing. Provide either (doctorKey AND patientKey) OR mixedKey', + required: true, + schema: { + $doctorKey: 'appointments/appointmentId/DOCTOR.webm', + $patientKey: 'appointments/appointmentId/PATIENT.webm', + $mixedKey: 'appointments/appointmentId/MIXED.webm' + } + } + #swagger.responses[202] = { + description: 'SOAP notes generated successfully', + schema: { + message: 'SOAP generated successfully', + messageAr: 'تم إنشاء ملاحظات SOAP بنجاح', + data: { + SOAP: { + subjective: 'string', + objective: 'string', + assessment: 'string', + plan: 'string' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad request - missing required audio keys' + } + #swagger.responses[401] = { + description: 'Unauthorized - missing or invalid token' + } + #swagger.responses[404] = { + description: 'Appointment not found' + } + */ + AuthMiddleware, this.aiAppointmentsController.processAudioAI ) } diff --git a/src/server.ts b/src/server.ts index 7f7fda8..0481752 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,13 +10,14 @@ import { AppointmentRoute } from './routes/appointment.route'; import { QueueRoute } from './routes/queue.route'; import { NurseRoute } from './routes/nurse.route'; import { UsersRoute } from './routes/user.route'; +import { AiAppointmentsRoute } from './routes/ai_appointments.route'; ValidateEnv(); const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), new SuperAdminRoute(), new DoctorsRoute(), new ClinicRoute(), new AppointmentRoute(), new QueueRoute(), - new UsersRoute(), new NurseRoute(), + new UsersRoute(), new NurseRoute(), new AiAppointmentsRoute() ]); app.listen(); diff --git a/src/swagger-output.json b/src/swagger-output.json index a344eed..67c2e5f 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -47,6 +47,10 @@ { "name": "Nurses", "description": "Nurse account endpoints" + }, + { + "name": "AI Appointments", + "description": "AI-generated SOAP notes for appointments" } ], "schemes": [ @@ -7350,6 +7354,64 @@ } } }, + "/appointments/{appointmentId}/agora-token": { + "get": { + "tags": [ + "Appointments" + ], + "description": "Get Agora token and channel name for a specific appointment", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "The ID of the appointment" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication (patient or doctor of the appointment)", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Agora token and channel name retrieved successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Agora token retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع توكن أجورا بنجاح" + }, + "data": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "string" + }, + "appId": { + "type": "string", + "example": "string" + } + } + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, "/queue/position/{appointmentId}": { "get": { "tags": [ @@ -8552,6 +8614,193 @@ } } } + }, + "/{appointmentId}/upload-url": { + "get": { + "tags": [ + "AI Appointments" + ], + "description": "Get a pre-signed upload URL for uploading audio files to S3", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "The ID of the appointment" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "userType", + "in": "query", + "description": "Type of user recording the audio (DOCTOR, PATIENT, or MIXED)", + "required": true, + "type": "string", + "enum": [ + "DOCTOR", + "PATIENT", + "MIXED" + ] + } + ], + "responses": { + "200": { + "description": "Upload URL generated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Upload URL generated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إنشاء رابط التحميل بنجاح" + }, + "data": { + "type": "object", + "properties": { + "uploadUrl": { + "type": "string", + "example": "string" + }, + "objectKey": { + "type": "string", + "example": "string" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - invalid userType parameter" + }, + "401": { + "description": "Unauthorized - missing or invalid token" + }, + "404": { + "description": "Appointment not found" + } + } + } + }, + "/{appointmentId}/process-audio-ai": { + "post": { + "tags": [ + "AI Appointments" + ], + "description": "Process audio recordings using AI to generate SOAP notes. Accepts either separate doctor/patient audio keys or a single mixed audio key", + "parameters": [ + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string", + "description": "The ID of the appointment" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Audio file keys for processing. Provide either (doctorKey AND patientKey) OR mixedKey", + "required": true, + "schema": { + "type": "object", + "properties": { + "doctorKey": { + "type": "string", + "example": "appointments/appointmentId/DOCTOR.webm" + }, + "patientKey": { + "type": "string", + "example": "appointments/appointmentId/PATIENT.webm" + }, + "mixedKey": { + "type": "string", + "example": "appointments/appointmentId/MIXED.webm" + } + }, + "required": [ + "doctorKey", + "patientKey", + "mixedKey" + ] + } + } + ], + "responses": { + "202": { + "description": "SOAP notes generated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "SOAP generated successfully" + }, + "messageAr": { + "type": "string", + "example": "تم إنشاء ملاحظات SOAP بنجاح" + }, + "data": { + "type": "object", + "properties": { + "SOAP": { + "type": "object", + "properties": { + "subjective": { + "type": "string", + "example": "string" + }, + "objective": { + "type": "string", + "example": "string" + }, + "assessment": { + "type": "string", + "example": "string" + }, + "plan": { + "type": "string", + "example": "string" + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Bad request - missing required audio keys" + }, + "401": { + "description": "Unauthorized - missing or invalid token" + }, + "404": { + "description": "Appointment not found" + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.mjs b/src/swagger.mjs index 2518576..78297b9 100644 --- a/src/swagger.mjs +++ b/src/swagger.mjs @@ -18,6 +18,7 @@ const doc = { { name: 'Queue', description: 'Queue endpoints' }, { name: 'Users', description: 'User account endpoints' }, { name: 'Nurses', description: 'Nurse account endpoints' }, + { name: 'AI Appointments', description: 'AI-generated SOAP notes for appointments' } ], }; @@ -25,6 +26,7 @@ const doc = { const outputFile = './swagger-output.json'; const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' - , './src/routes/appointment.route.ts', './src/routes/queue.route.ts', './src/routes/user.route.ts', './src/routes/nurse.route.ts']; + , './src/routes/appointment.route.ts', './src/routes/queue.route.ts', './src/routes/user.route.ts', './src/routes/nurse.route.ts' + , './src/routes/ai_appointments.route.ts']; swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 55c2e12..984e975 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -332,6 +332,10 @@ export const SuccessResponseMessages = { message_en: "AI processing started successfully.", message_ar: "تم بدء المعالجة بالذكاء الاصطناعي بنجاح.", }, + SOAP_GENERATED: { + message_en: "SOAP notes generated successfully.", + message_ar: "تم إنشاء ملاحظات SOAP بنجاح.", + }, } From e0b897f727a486a936d71288e095896b01b22223 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 6 Mar 2026 19:56:57 +0200 Subject: [PATCH 183/210] update queue/socket service --- src/controllers/appointment.controller.ts | 59 ++++++--- src/interfaces/appointments.interface.ts | 19 +++ src/services/appointment.service.ts | 138 +++++++++++++++++++++- src/services/socket.service.ts | 76 ++++++------ 4 files changed, 235 insertions(+), 57 deletions(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 380058e..a89632a 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -3,6 +3,7 @@ import { RequestWithUser } from "@/interfaces"; import { HttpException } from "@/exceptions/HttpException"; import { catchAsync } from '@/utils/catchAsync'; import { AppointmentService } from "@/services/appointment.service" +import { AppointmentStatusChangedPayload } from "@/interfaces"; import Container from "typedi"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { SuccessResponseMessages, createMultiLangMessage } from '@/utils/responseMessages'; @@ -11,7 +12,7 @@ import { SocketService } from "@/services/socket.service"; export class AppointmentController { public appointmentService = Container.get(AppointmentService); - public socketService = new SocketService(); + public socketService = Container.get(SocketService); public getAvailableDays = catchAsync(async (req: Request, res: Response): Promise => { const { doctorId } = req.params; @@ -139,20 +140,19 @@ export class AppointmentController { const { appointmentId } = req.params; const { newScheduledTime } = req.body; - // const { doctorId, scheduledTime } = await this.appointmentService.getAppointmentOwners(appointmentId); + const result = await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); - await this.appointmentService.rescheduleAppointmentByPatient(patientId, appointmentId, new Date(newScheduledTime)); + const payload: AppointmentStatusChangedPayload = { + appointmentId, + newStatus: 'CONFIRMED', + doctorId: result.doctorId, + patientId: result.patientId, + patientName: result.patientName, + appointmentDate: result.appointmentDate, + startTime: result.startTime, + }; + await this.socketService.emitAppointmentStatusChanged(payload); - // await this.socketService.emitQueueUpdatesToPatients(doctorId, new Date(scheduledTime)); - // await this.socketService.emitQueueUpdatesToPatients(doctorId, new Date(newScheduledTime)); - - // this.socketService.emitToUser(doctorId, 'appointment_rescheduled_by_patient', { - // appointmentId, - // patientId, - // oldScheduledTime: scheduledTime, - // newScheduledTime: new Date(newScheduledTime).toISOString(), - // }); - // idk const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response @@ -163,7 +163,19 @@ export class AppointmentController { const userId = req.user.id; const { appointmentId } = req.params; - await this.appointmentService.cancelAppointment(userId, appointmentId); + const result = await this.appointmentService.cancelAppointment(userId, appointmentId); + const payload: AppointmentStatusChangedPayload = { + appointmentId, + newStatus: 'CANCELLED', + doctorId: result.doctorId, + patientId: result.patientId, + patientName: result.patientName, + appointmentDate: result.appointmentDate, + startTime: result.startTime, + }; + await this.socketService.emitAppointmentStatusChanged(payload); + await this.socketService.emitQueueUpdatesToPatients(result.doctorId, new Date(result.appointmentDate)); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_CANCELLED_SUCCESSFULLY); res.status(200).json({ ...response @@ -185,7 +197,24 @@ export class AppointmentController { throw new HttpException(error.status, error.message, error.messageAr); } - await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes); + const affectedAppointments = await this.appointmentService.rescheduleAppointmentByDoctor(doctorId, appointmentId, minutes); + for (const appointment of affectedAppointments) { + const payload: AppointmentStatusChangedPayload = { + appointmentId: appointment.appointmentId, + newStatus: 'CONFIRMED', + doctorId: appointment.doctorId, + patientId: appointment.patientId, + patientName: appointment.patientName, + appointmentDate: appointment.appointmentDate, + startTime: appointment.startTime, + }; + await this.socketService.emitAppointmentStatusChanged(payload); + } + + if (affectedAppointments.length > 0) { + await this.socketService.emitQueueUpdatesToPatients(doctorId, new Date(affectedAppointments[0].appointmentDate)); + } + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_RESCHEDULED_SUCCESSFULLY); res.status(200).json({ ...response diff --git a/src/interfaces/appointments.interface.ts b/src/interfaces/appointments.interface.ts index ff2e7ab..603da9b 100644 --- a/src/interfaces/appointments.interface.ts +++ b/src/interfaces/appointments.interface.ts @@ -151,4 +151,23 @@ export interface AppointmentData { appointment_date: string; start_time: string; end_time: string; +} + +export interface AppointmentStatusChangedPayload { + appointmentId: string; + newStatus: AppointmentStatus; + doctorId: string; + patientId: string; + patientName: string; + appointmentDate: string; + startTime: string; +} + +export interface AppointmentEventData { + appointmentId: string; + doctorId: string; + patientId: string; + patientName: string; + appointmentDate: string; + startTime: string; } \ No newline at end of file diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index c155f34..f6de757 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -5,7 +5,7 @@ import { Service, Container } from 'typedi'; import { TimeSlot } from '@/interfaces'; import { HttpException } from "@/exceptions/HttpException"; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; -import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations, AppointmentData } from '@/interfaces/appointments.interface'; +import { PatientTodayAppointment, DoctorAppointment, DoctorScheduleDay, PatientAppointment, DoctorSchedule, checkExistingAppointments, ConflictingAppointment, DoctorVacations, Vacations, AppointmentData, AppointmentEventData } from '@/interfaces/appointments.interface'; import { QueueService } from './queue.service'; import { start } from 'repl'; @@ -473,7 +473,25 @@ export class AppointmentService { } - public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { + public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId + }, + select: { + id: true, + patient_id: true, + doctor_id: true, + scheduled_time: true, + deleted_at: true, + patient: { + select: { + name: true + } + } + } + }); + const slotDuration = await prisma.appointment.findUnique({ where: { id: appointmentId, @@ -496,10 +514,19 @@ export class AppointmentService { } }); + return { + appointmentId: appointment.id, + doctorId: appointment.doctor_id, + patientId: appointment.patient_id, + patientName: appointment.patient.name, + appointmentDate: this.formatDate(appointment.scheduled_time), + startTime: this.formatTime(appointment.scheduled_time), + }; + // penalty to be added later } - public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes: number): Promise { + public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes: number): Promise { const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); const appointments = await prisma.appointment.findMany({ where: { @@ -511,13 +538,34 @@ export class AppointmentService { } }, select: { - id: true + id: true, + patient_id: true, + doctor_id: true, + scheduled_time: true, + patient: { + select: { + name: true, + } + } } }) for (const { id: appointmentId } of appointments) { await this.rescheduleSingleAppointment(doctorId, appointmentId, minutes); } + + return appointments.map(appointment => { + const newScheduledTime = new Date(appointment.scheduled_time.getTime() + minutes * 60000); + return { + appointmentId: appointment.id, + doctorId: appointment.doctor_id, + patientId: appointment.patient_id, + patientName: appointment.patient.name, + appointmentDate: this.formatDate(newScheduledTime), + startTime: this.formatTime(newScheduledTime), + }; + }); + } public async enterDoctorSchedule(doctorId: string, clinicId: string | null, workingDay: number, startTime: string, endTime: string, slotDuration: number, bufferTime: number, isOnline: boolean): Promise { @@ -653,17 +701,23 @@ export class AppointmentService { }); } - public async cancelAppointment(userId: string, appointmentId: string): Promise { + public async cancelAppointment(userId: string, appointmentId: string): Promise { // see whether the user is patient or doctor const appointment = await prisma.appointment.findUnique({ where: { - id: appointmentId, + id: appointmentId }, select: { id: true, patient_id: true, doctor_id: true, + scheduled_time: true, deleted_at: true, + patient: { + select: { + name: true + } + } } }); @@ -693,6 +747,16 @@ export class AppointmentService { status: 'CANCELLED', } }); + + return { + appointmentId: appointment.id, + doctorId: appointment.doctor_id, + patientId: appointment.patient_id, + patientName: appointment.patient.name, + appointmentDate: this.formatDate(appointment.scheduled_time), + startTime: this.formatTime(appointment.scheduled_time), + }; + // penalty to be added later }; @@ -1331,6 +1395,63 @@ export class AppointmentService { } + public async getNurseAppointmentsToday(nurseId: string): Promise { + const crrentDate = new Date(); + const today = this.formatDate(crrentDate); + const dayOfWeek = this.getDayOfWeek(crrentDate.getUTCDay()); + + const nurseSchedules = await prisma.nurseSchedule.findMany({ + where: { + nurse_id: nurseId, + day_of_week: dayOfWeek, + is_active: true, + deleted_at: null, + }, + select: { + doctor_id: true, + clinic_id: true + } + }); + + if (!nurseSchedules.length) { + return []; + } + + const allAppointments = await Promise.all( + nurseSchedules.map(schedule => + this.getAppointmentsByDate(schedule.doctor_id, schedule.clinic_id, today) + ) + ); + + return allAppointments.flat(); + } + + public async getAppointmentsForDay(doctorId: string, date: Date): Promise<{ id: string; patient_id: string }[]> { + const startOfDay = new Date(date); + startOfDay.setHours(0, 0, 0, 0); + + const endOfDay = new Date(date); + endOfDay.setHours(23, 59, 59, 999); + + + return prisma.appointment.findMany({ + where: { + doctor_id: doctorId, + scheduled_time: { + gte: startOfDay, + lte: endOfDay + }, + status: { in: ['CONFIRMED'] }, + deleted_at: null, + }, + select: { + id: true, + patient_id: true + + }, + }); + } + private async getConflictingAppointments(doctorId: string, scheduleId: string, breakStart?: string, breakEnd?: string): Promise { const schedule = await prisma.doctorSchedule.findUnique({ where: { @@ -1550,6 +1671,11 @@ export class AppointmentService { slot_duration: true, status: true, deleted_at: true, + patient: { + select: { + name: true, + } + } } }); diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 594af90..43223ea 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -3,16 +3,24 @@ import { Server, Socket } from 'socket.io'; import { verify } from 'jsonwebtoken'; import { SocketStoredInToken } from '@/interfaces'; import { SECRET_KEY } from '@/config'; -import prisma from '@/config/prisma'; import { AppointmentService } from './appointment.service'; +import { AppointmentStatusChangedPayload } from '@/interfaces'; import { QueueService } from './queue.service'; -import { Container } from 'typedi'; +import { Container, Service } from 'typedi'; interface AuthenticatedSocket extends Socket { userId?: string; userRole?: string; } +function parseCookies(cookieHeader: string = ''): Record { + return Object.fromEntries( + cookieHeader.split(';').map(c => c.trim().split('=').map(decodeURIComponent)) + ); +} + + +@Service() export class SocketService { private io: Server; // userId --> set of socketIds (each tab/device = different socketId) @@ -25,7 +33,7 @@ export class SocketService { cors: { origin: process.env.ORIGIN, credentials: true, - + }, // polling is just a fallback if websocket fails transports: ['websocket', 'polling'], @@ -47,7 +55,7 @@ export class SocketService { private async authMiddleware(socket: AuthenticatedSocket, next: (err?: Error) => void): Promise { try { - const token = socket.handshake.auth.token || socket.handshake.headers['authorization']?.split(' ')[1]; + const token = parseCookies(socket.handshake.headers.cookie)['Authorization']?.replace(/^Bearer\s+/i, ''); if (!token) { return next(new Error('Authentication error: Token not provided')); } @@ -56,7 +64,8 @@ export class SocketService { socket.userId = decoded.id; socket.userRole = decoded.role; next(); - } catch (error) { + } + catch (error) { next(new Error('Authentication error: Invalid token')); } } @@ -89,9 +98,13 @@ export class SocketService { if (userRole === 'PATIENT') { this.sendInitialPatientData(userId); - } else if (userRole === 'DOCTOR') { + } + else if (userRole === 'DOCTOR') { this.sendInitialDoctorData(userId); } + else if (userRole === 'NURSE') { + this.sendInitialNurseData(userId); + } } @@ -114,60 +127,51 @@ export class SocketService { } } - public async emitQueueUpdatesToPatients(doctorId: string, date: Date): Promise { - const appointments = await this.getAppointmentsForDay(doctorId, date); + public async emitQueueUpdatesToPatients(doctorId: string, date?: Date): Promise { + const appointments = await this.appointmentService.getAppointmentsForDay(doctorId, date); for (const app of appointments) { const queuePosition = await this.queueService.getQueuePosition(app.id); this.emitToUser(app.patient_id, 'queue_updated', queuePosition); } } - private async getAppointmentsForDay(doctorId: string, date: Date): Promise<{ id: string; patient_id: string }[]> { - const startOfDay = new Date(date); - startOfDay.setHours(0, 0, 0, 0); - - const endOfDay = new Date(date); - endOfDay.setHours(23, 59, 59, 999); - - - return prisma.appointment.findMany({ - where: { - doctor_id: doctorId, - scheduled_time: { - gte: startOfDay, - lte: endOfDay - }, - status: { in: ['CONFIRMED'] }, - deleted_at: null, - }, - select: { - id: true, - patient_id: true - - }, - }); + public async emitAppointmentStatusChanged(payload: AppointmentStatusChangedPayload) { + this.emitToUser(payload.doctorId, 'appointment_status_changed', payload); + this.emitToUser(payload.patientId, 'appointment_status_changed', payload); } + private async sendInitialPatientData(patientId: string): Promise { try { const appointments = await this.appointmentService.getPatientAppointments(patientId); const appointmentsWithQueue = await Promise.all(appointments.map(async (app) => { - await this.queueService.calculateQueuePosition(app.id); // Ensure up-to-date + await this.queueService.calculateQueuePosition(app.id); const queuePosition = await this.queueService.getQueuePosition(app.id); return { ...app, queuePosition }; })); this.emitToUser(patientId, 'initial_data', { appointments: appointmentsWithQueue }); - } catch (error) { + } + catch (error) { console.error('Error sending initial patient data:', error); } } private async sendInitialDoctorData(doctorId: string): Promise { try { - const schedule = await this.appointmentService.getDoctorSchedule(doctorId); + const schedule = await this.appointmentService.getCurrentDoctorSchedule(doctorId); this.emitToUser(doctorId, 'initial_data', { schedule }); } catch (error) { console.error('error sending initial doctor data:', error); } } -} \ No newline at end of file + + private async sendInitialNurseData(nurseId: string): Promise { + try { + const appointments = await this.appointmentService.getNurseAppointmentsToday(nurseId); + this.emitToUser(nurseId, 'initial_data', { appointments }); + } + catch (error) { + console.error('Error sending initial nurse data:', error); + } + } +} From 8537150d66979fee4669e443a0cf533adede663e Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 7 Mar 2026 01:17:47 +0200 Subject: [PATCH 184/210] solve timezone issues --- src/services/appointment.service.ts | 36 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index f6de757..4f64def 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -379,11 +379,8 @@ export class AppointmentService { public async getTodayAppointment(patientId: string): Promise { const result: PatientTodayAppointment[] = []; - const today = new Date(); - today.setUTCHours(0, 0, 0, 0); - - const endOfToday = new Date(); - endOfToday.setUTCHours(23, 59, 59, 999); + const now = new Date(); + const { start: today, end: endOfToday } = this.getTodayBoundaries(now); const appointments = await prisma.appointment.findMany({ where: { @@ -562,7 +559,7 @@ export class AppointmentService { patientId: appointment.patient_id, patientName: appointment.patient.name, appointmentDate: this.formatDate(newScheduledTime), - startTime: this.formatTime(newScheduledTime), + startTime: this.formatTime(newScheduledTime), }; }); @@ -1176,11 +1173,8 @@ export class AppointmentService { } public async getCurrentDoctorSchedule(doctorId: string): Promise { - const startOfDay = new Date(); - startOfDay.setUTCHours(0, 0, 0, 0); - - const endOfDay = new Date(); - endOfDay.setUTCHours(23, 59, 59, 999); + const now = new Date(); + const { start: startOfDay, end: endOfDay } = this.getTodayBoundaries(now); const appointments = await prisma.appointment.findMany({ where: { @@ -1427,12 +1421,7 @@ export class AppointmentService { } public async getAppointmentsForDay(doctorId: string, date: Date): Promise<{ id: string; patient_id: string }[]> { - const startOfDay = new Date(date); - startOfDay.setHours(0, 0, 0, 0); - - const endOfDay = new Date(date); - endOfDay.setHours(23, 59, 59, 999); - + const { start: startOfDay, end: endOfDay } = this.getTodayBoundaries(date); return prisma.appointment.findMany({ where: { @@ -1777,4 +1766,17 @@ export class AppointmentService { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } + private getTodayBoundaries(date: Date, timezone: string = 'Africa/Cairo'): { start: Date; end: Date } { + + const localDateStr = new Intl.DateTimeFormat('en-CA', { + timeZone: timezone, + year: 'numeric', month: '2-digit', day: '2-digit' + }).format(date); + + const start = new Date(`${localDateStr}T00:00:00+02:00`); + const end = new Date(`${localDateStr}T23:59:59.999+02:00`); + + return { start, end }; + } + } \ No newline at end of file From 881cbebc20b91f10252230129f584c9591878ea9 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 7 Mar 2026 02:36:03 +0200 Subject: [PATCH 185/210] feat: integrate ipfs with fabric --- src/controllers/fabric.controller.ts | 55 +-- src/controllers/medical-records.controller.ts | 7 + src/dtos/fabric-identity.dto.ts | 2 +- src/dtos/medicalRecord.dto.ts | 38 +- src/interfaces/fabric-identity.interface.ts | 4 +- src/interfaces/medical-records.interface.ts | 11 +- .../migration.sql | 5 + src/prisma/schema.prisma | 5 +- src/routes/fabric.route.ts | 101 ++-- src/routes/medical-record.route.ts | 38 +- src/services/fabric.service.ts | 467 +++++++++--------- src/services/identity-storage.service.ts | 89 ++-- src/services/ipfs.service.ts | 9 + src/services/key-management.service.ts | 66 ++- src/services/medical-records.service.ts | 58 ++- src/swagger-output.json | 278 +++++++++++ src/swagger.mjs | 19 +- 17 files changed, 770 insertions(+), 482 deletions(-) create mode 100644 src/prisma/migrations/20260307000000_move_key_to_blockchain/migration.sql diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts index 6d9f423..67f5246 100644 --- a/src/controllers/fabric.controller.ts +++ b/src/controllers/fabric.controller.ts @@ -8,24 +8,15 @@ class FabricController { public fabricService = new FabricService(); - private getIdentityLabel(req: Request): string { - const identityLabel = req.headers['x-fabric-identity'] as string; - if (!identityLabel) { - throw new HttpException(400, 'Missing X-Fabric-Identity header'); - } - return identityLabel; - } - - public onboardIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { try { const input: FabricIdentityInput = req.body; // Validate required fields - if (!input.label || !input.mspId || !input.certificate || + if (!input.clinicId || !input.mspId || !input.certificate || !input.privateKey || !input.peerEndpoint || !input.peerHostAlias || !input.tlsCertificate) { - throw new HttpException(400, 'Missing required fields: label, mspId, certificate, privateKey, peerEndpoint, peerHostAlias, tlsCertificate'); + throw new HttpException(400, 'Missing required fields: clinicId, mspId, certificate, privateKey, peerEndpoint, peerHostAlias, tlsCertificate'); } const identity = await identityStorage.storeIdentity(input); @@ -51,14 +42,9 @@ class FabricController { public deleteIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const label = req.params.label; - - // Close any active connection for this identity - await this.fabricService.closeConnection(label); - - // Delete from storage - await identityStorage.deleteIdentity(label); - + const clinicId = req.params.clinicId; + await this.fabricService.closeConnection(clinicId); + await identityStorage.deleteIdentity(clinicId); res.status(200).json({ message: 'Identity deleted successfully' }); } catch (error) { next(error); @@ -76,20 +62,20 @@ class FabricController { public getAllRecords = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); - const records = await this.fabricService.getAllRecords(identityLabel); + const clinicId = req.params.clinicId; + const records = await this.fabricService.getAllRecords(clinicId); res.status(200).json({ data: records, message: 'findAll' }); } catch (error) { next(error); } }; - public getRecordById = async (req: Request, res: Response, next: NextFunction): Promise => { + public getRecordsByPatient = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); + const clinicId = req.params.clinicId; const patientId = req.params.patientId; - const record = await this.fabricService.getRecordByPatientId(identityLabel, patientId); - res.status(200).json({ data: record, message: 'findOne' }); + const records = await this.fabricService.getRecordsByPatient(clinicId, patientId); + res.status(200).json({ data: records, message: 'findAll' }); } catch (error) { next(error); } @@ -97,8 +83,8 @@ class FabricController { public addRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); - await this.fabricService.addRecord(identityLabel, req.body); + const clinicId = req.params.clinicId; + await this.fabricService.addRecord(clinicId, req.body); res.status(201).json({ message: 'created' }); } catch (error) { next(error); @@ -107,9 +93,9 @@ class FabricController { public updateRecord = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); + const clinicId = req.params.clinicId; const patientId = req.params.patientId; - await this.fabricService.updateRecord(identityLabel, patientId, req.body); + await this.fabricService.updateRecord(clinicId, patientId, req.body); res.status(200).json({ message: 'updated' }); } catch (error) { next(error); @@ -118,7 +104,7 @@ class FabricController { public grantAccess = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); + const clinicId = req.params.clinicId; const patientId = req.params.patientId; const { targetMsp } = req.body; @@ -126,7 +112,7 @@ class FabricController { throw new HttpException(400, 'targetMsp is required'); } - await this.fabricService.grantAccess(identityLabel, patientId, targetMsp); + await this.fabricService.grantAccess(clinicId, patientId, targetMsp); res.status(200).json({ message: 'Access granted successfully' }); } catch (error) { next(error); @@ -135,9 +121,10 @@ class FabricController { public initLedger = async (req: Request, res: Response, next: NextFunction): Promise => { try { - const identityLabel = this.getIdentityLabel(req); - await this.fabricService.initLedger(identityLabel); - res.status(200).json({ message: 'Ledger initialized' }); + const clinicId = req.params.clinicId; + const backupData = Array.isArray(req.body?.backupData) ? req.body.backupData : []; + await this.fabricService.initLedger(clinicId, backupData); + res.status(200).json({ message: 'Ledger initialized', seeded: backupData.length }); } catch (error) { next(error); } diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index 7a66ac0..323e207 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -18,11 +18,13 @@ export class MedicalRecordController { const recordData: CreateMedicalRecordDto = req.body; const patientId = req.user.id; const doctorId = req.params.doctorId; + const clinicId = req.params.clinicId; const fileBuffer = req.file.buffer; const fileName = req.file.originalname; const mimeType = req.file.mimetype; await this.medicalRecordService.createMedicalRecord( + clinicId, patientId, doctorId, recordData, @@ -62,6 +64,11 @@ export class MedicalRecordController { }); }); + public checkIpfsHealth = catchAsync(async (req: Request, res: Response): Promise => { + const result = await this.medicalRecordService.checkIpfsHealth(); + res.status(200).json(result); + }); + public deleteRecord = catchAsync(async (req: Request, res: Response): Promise => { const recordId = req.params.id; diff --git a/src/dtos/fabric-identity.dto.ts b/src/dtos/fabric-identity.dto.ts index 0820560..4738c84 100644 --- a/src/dtos/fabric-identity.dto.ts +++ b/src/dtos/fabric-identity.dto.ts @@ -2,7 +2,7 @@ import { IsString, IsOptional, IsNotEmpty } from 'class-validator'; export class OnboardIdentityDto { @IsString() @IsNotEmpty() - public label: string; + public clinicId: string; @IsString() @IsNotEmpty() diff --git a/src/dtos/medicalRecord.dto.ts b/src/dtos/medicalRecord.dto.ts index a17c58a..9a9f2c6 100644 --- a/src/dtos/medicalRecord.dto.ts +++ b/src/dtos/medicalRecord.dto.ts @@ -1,21 +1,43 @@ -import { IsEnum, IsNotEmpty, IsOptional, IsUUID, IsString } from 'class-validator'; -import { RecordType } from '@prisma/client'; +import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; export class CreateMedicalRecordDto { + @IsUUID() + @IsNotEmpty() + public patientId: string; + + @IsUUID() + @IsNotEmpty() + public recordId: string; + + @IsUUID() + @IsNotEmpty() + public doctorId: string; + @IsString() @IsNotEmpty() - public name: string; + public type: string; - @IsEnum(RecordType) + @IsString() @IsNotEmpty() - public type: RecordType; + public ipfsCidKey: string; +} + +export class UpdateMedicalRecordDto { @IsUUID() @IsNotEmpty() - public clinicId: string; + public recordId: string; @IsUUID() + @IsNotEmpty() + public doctorId: string; + + @IsString() + @IsNotEmpty() + public type: string; + + @IsString() @IsOptional() - public appointmentId?: string; -} \ No newline at end of file + public ipfsCidKey?: string; +} diff --git a/src/interfaces/fabric-identity.interface.ts b/src/interfaces/fabric-identity.interface.ts index 2e7a447..6b1ea64 100644 --- a/src/interfaces/fabric-identity.interface.ts +++ b/src/interfaces/fabric-identity.interface.ts @@ -1,6 +1,6 @@ export interface FabricIdentity { - label: string; + clinicId: string; mspId: string; certificate: string; privateKey: string; @@ -14,7 +14,7 @@ export interface FabricIdentity { } export interface FabricIdentityInput { - label: string; + clinicId: string; mspId: string; certificate: string; privateKey: string; diff --git a/src/interfaces/medical-records.interface.ts b/src/interfaces/medical-records.interface.ts index 9eb8113..542a54c 100644 --- a/src/interfaces/medical-records.interface.ts +++ b/src/interfaces/medical-records.interface.ts @@ -1,12 +1,9 @@ export interface MedicalRecord { patientId: string; - firstName: string; - lastName: string; - dateOfBirth: string; - gender: string; - bloodType: string; - ipfsCid: string; - summary?: string; + recordId: string; + doctorId: string; + type: string; + ipfsCidKey: string; ownerMsp?: string; authorizedMsps?: string[]; } diff --git a/src/prisma/migrations/20260307000000_move_key_to_blockchain/migration.sql b/src/prisma/migrations/20260307000000_move_key_to_blockchain/migration.sql new file mode 100644 index 0000000..b02ec0e --- /dev/null +++ b/src/prisma/migrations/20260307000000_move_key_to_blockchain/migration.sql @@ -0,0 +1,5 @@ +-- Drop the foreign key constraint and the key_id column from MedicalRecords +-- since encryption keys are now stored on the Hyperledger Fabric blockchain. + +ALTER TABLE "MedicalRecords" DROP CONSTRAINT IF EXISTS "MedicalRecords_key_id_fkey"; +ALTER TABLE "MedicalRecords" DROP COLUMN IF EXISTS "key_id"; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index ace7e33..1ff9e1d 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -251,21 +251,19 @@ model AuditLog { model MedicalRecord { id String @id @default(uuid()) patient_id String - clinic_id String + clinic_id String doctor_id String? appointment_id String? name String @db.VarChar(255) cid String @unique @db.VarChar(255) mime_type String @db.VarChar(100) type RecordType - key_id String created_at DateTime @default(now()) modified_at DateTime @updatedAt deleted_at DateTime? patient User @relation("PatientMedicalRecords", fields: [patient_id], references: [id]) appointment Appointment? @relation(fields: [appointment_id], references: [id]) - encryption_key EncryptionKey @relation(fields: [key_id], references: [id]) @@index([patient_id]) @@index([clinic_id]) @@ -285,7 +283,6 @@ model EncryptionKey { deleted_at DateTime? patient User @relation("PatientEncryptionKey", fields: [patient_id], references: [id], onDelete: Cascade) - medical_records MedicalRecord[] @@map("EncryptionKeys") } diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index d7460bf..c77a721 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -25,7 +25,7 @@ export class FabricRoute implements Routes { description: 'Identity onboarding data', required: true, schema: { - $label: 'org1', + $clinicId: 'clinic-uuid-here', $mspId: 'Org1MSP', $certificate: 'PEM certificate', $privateKey: 'PEM private key', @@ -46,7 +46,7 @@ export class FabricRoute implements Routes { this.fabricController.listIdentities, ); this.router.delete( - '/fabric/identities/:label', + '/fabric/identities/:clinicId', /* #swagger.tags = ['FabricIdentity'] */ this.fabricController.deleteIdentity, ); @@ -57,13 +57,25 @@ export class FabricRoute implements Routes { ); this.router.post( '/fabric/init-ledger', - /* + /* #swagger.tags = ['FabricIdentity'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' + #swagger.description = 'Initialize the ledger, optionally seeding it with backup records' + #swagger.parameters['body'] = { + in: 'body', + description: 'Optional backup data to seed the ledger', + required: false, + schema: { + backupData: [ + { + patientId: 'patient-uuid', + recordId: 'record-uuid', + doctorId: 'doctor-uuid', + type: 'LAB_RESULT', + ownerMsp: 'Org1MSP', + authorizedMsps: [] + } + ] + } } */ this.fabricController.initLedger, @@ -92,40 +104,26 @@ export class FabricRoute implements Routes { ); this.router.get( '/records/:patientId', - /* + /* #swagger.tags = ['MedicalRecords'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' - } + #swagger.description = 'Get all records for a patient (authorized MSPs only)' */ - this.fabricController.getRecordById, + this.fabricController.getRecordsByPatient, ); this.router.post( '/records', - /* + /* #swagger.tags = ['MedicalRecords'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' - } + #swagger.description = 'Add a new medical record for a patient' #swagger.parameters['body'] = { in: 'body', - description: 'Medical record data', required: true, schema: { - $patientId: 'P12345', - $firstName: 'John', - $lastName: 'Doe', - $dateOfBirth: '1990-01-01', - $gender: 'Male', - $bloodType: 'O+', - $ipfsCid: 'Qm...', - summary: 'Optional summary' + $patientId: 'patient-uuid', + $recordId: 'record-uuid', + $doctorId: 'doctor-uuid', + $type: 'LAB_RESULT', + $ipfsCidKey: 'bafybeigdyrzt...' } } */ @@ -133,27 +131,18 @@ export class FabricRoute implements Routes { this.fabricController.addRecord, ); this.router.put( - '/records/:patientId', - /* + '/records/:patientId/:recordId', + /* #swagger.tags = ['MedicalRecords'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' - } + #swagger.description = 'Update an existing medical record (doctorId, type, optional new ipfsCidKey via transient)' #swagger.parameters['body'] = { in: 'body', - description: 'Update medical record data', required: true, schema: { - $firstName: 'John', - $lastName: 'Doe', - $dateOfBirth: '1990-01-01', - $gender: 'Male', - $bloodType: 'O+', - $ipfsCid: 'Qm...', - summary: 'Optional summary' + $recordId: 'uuid-record-id', + $doctorId: 'doctor-uuid', + $type: 'LAB_RESULT', + ipfsCidKey: 'optional-new-cid-key' } } */ @@ -163,21 +152,15 @@ export class FabricRoute implements Routes { this.router.post( '/records/:patientId/access', - /* + /* #swagger.tags = ['MedicalRecords'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' - } + #swagger.description = 'Grant access to all records of a patient for a target MSP' #swagger.parameters['body'] = { in: 'body', - description: 'Grant access to MSP', required: true, - schema: { - $targetMsp: 'Org2MSP' - } + schema: { $targetMsp: 'Org2MSP' } + } + */ } */ this.fabricController.grantAccess, diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 172dce3..4921eaf 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -18,6 +18,24 @@ export class MedicalRecordRoute implements Routes { private initializeRoutes() { + this.router.get( + `${this.path}/health/ipfs`, + /* + #swagger.path = '/record/health/ipfs' + #swagger.method = 'get' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Checks connectivity to the IPFS (Pinata) service' + #swagger.responses[200] = { + description: 'IPFS connection is healthy', + schema: { status: 'ok', message: 'IPFS connection is healthy' } + } + #swagger.responses[503] = { + description: 'IPFS service is unreachable' + } + */ + this.medicalRecordController.checkIpfsHealth + ); + this.router.get( `${this.path}/patient`, /* @@ -98,12 +116,12 @@ export class MedicalRecordRoute implements Routes { ); this.router.post( - `${this.path}/:doctorId/upload`, + `${this.path}/:clinicId/:doctorId/upload`, /* - #swagger.path = '/record/{doctorId}/upload' + #swagger.path = '/record/{clinicId}/{doctorId}/upload' #swagger.method = 'post' #swagger.tags = ['Medical Records'] - #swagger.description = 'Patient uploads a new medical record file' + #swagger.description = 'Patient uploads a new medical record file. The clinic identity is used to store the encryption key on the blockchain.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -112,6 +130,13 @@ export class MedicalRecordRoute implements Routes { type: 'string' } + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'UUID of the clinic whose Fabric identity will store the encryption key', + required: true, + type: 'string' + } + #swagger.parameters['doctorId'] = { in: 'path', description: 'UUID of the doctor associated with this record', @@ -140,13 +165,6 @@ export class MedicalRecordRoute implements Routes { type: 'string' } - #swagger.parameters['clinicId'] = { - in: 'formData', - description: 'UUID of the clinic', - required: true, - type: 'string' - } - #swagger.parameters['appointmentId'] = { in: 'formData', description: 'UUID of the appointment (optional)', diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index 0ebdb54..08aa4c8 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -7,267 +7,252 @@ import { MedicalRecord } from '@/interfaces/medical-records.interface'; import { FabricIdentity } from '@/interfaces/fabric-identity.interface'; import identityStorage from '@/services/identity-storage.service'; - interface GatewayConnection { - gateway: Gateway; - client: grpc.Client; - contract: Contract; - identity: FabricIdentity; - lastUsed: Date; + gateway: Gateway; + client: grpc.Client; + contract: Contract; + identity: FabricIdentity; + lastUsed: Date; } class FabricService { - private readonly utf8Decoder = new TextDecoder(); - - // Connection cache with TTL - private connections: Map = new Map(); - private readonly CONNECTION_TTL_MS = 30 * 60 * 1000; // 30 minutes - private cleanupInterval: NodeJS.Timeout | null = null; - - constructor() { - this.startCleanupInterval(); - } - - public async getGatewayConnection(identityLabel: string): Promise { - - const cached = this.connections.get(identityLabel); - if (cached) { - cached.lastUsed = new Date(); - return cached; - } - - const identity = await identityStorage.getIdentity(identityLabel); - const connection = await this.createConnection(identity); - this.connections.set(identityLabel, connection); - - console.log(`✅ Created new gateway connection for: ${identityLabel}`); - return connection; - } - - private async createConnection(identity: FabricIdentity): Promise { - try { - - const client = await this.newGrpcConnection(identity); - - - const gateway = connect({ - client, - identity: this.createIdentity(identity), - signer: this.createSigner(identity), - }); - - - const network = gateway.getNetwork(identity.channelName); - const contract = network.getContract(identity.chaincodeName); - - return { - gateway, - client, - contract, - identity, - lastUsed: new Date(), - }; - } catch (error: any) { - console.error(`❌ Failed to create connection for ${identity.label}:`, error.message); - throw new HttpException(503, `Failed to connect to Fabric network: ${error.message}`); - } - } - - private async newGrpcConnection(identity: FabricIdentity): Promise { - const tlsRootCert = Buffer.from(identity.tlsCertificate); - const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); - - return new grpc.Client(identity.peerEndpoint, tlsCredentials, { - 'grpc.ssl_target_name_override': identity.peerHostAlias, - 'grpc.keepalive_time_ms': 120000, - 'grpc.http2.min_time_between_pings_ms': 120000, - 'grpc.keepalive_timeout_ms': 20000, - 'grpc.http2.max_pings_without_data': 0, - 'grpc.keepalive_permit_without_calls': 1, - }); - } - - private createIdentity(identity: FabricIdentity): Identity { - return { - mspId: identity.mspId, - credentials: Buffer.from(identity.certificate), - }; - } - - private createSigner(identity: FabricIdentity): Signer { - const privateKey = crypto.createPrivateKey(identity.privateKey); - return signers.newPrivateKeySigner(privateKey); + private readonly utf8Decoder = new TextDecoder(); + + // Connection cache with TTL + private connections: Map = new Map(); + private readonly CONNECTION_TTL_MS = 30 * 60 * 1000; // 30 minutes + private cleanupInterval: NodeJS.Timeout | null = null; + + constructor() { + this.startCleanupInterval(); + } + + public async getGatewayConnection(clinicId: string): Promise { + const cached = this.connections.get(clinicId); + if (cached) { + cached.lastUsed = new Date(); + return cached; } - public async initLedger(identityLabel: string): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Submit Transaction: InitLedger (${identityLabel})`); - await contract.submitTransaction('InitLedger'); - console.log('*** InitLedger transaction committed successfully'); + const identity = await identityStorage.getIdentity(clinicId); + const connection = await this.createConnection(identity); + this.connections.set(clinicId, connection); + + console.log(`✅ Created new gateway connection for clinic: ${clinicId}`); + return connection; + } + + private async createConnection(identity: FabricIdentity): Promise { + try { + const client = await this.newGrpcConnection(identity); + + const gateway = connect({ + client, + identity: this.createIdentity(identity), + signer: this.createSigner(identity), + }); + + const network = gateway.getNetwork(identity.channelName); + const contract = network.getContract(identity.chaincodeName); + + return { + gateway, + client, + contract, + identity, + lastUsed: new Date(), + }; + } catch (error: any) { + console.error(`❌ Failed to create connection for clinic ${identity.clinicId}:`, error.message); + throw new HttpException(503, `Failed to connect to Fabric network: ${error.message}`); } - - - public async getAllRecords(identityLabel: string): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Evaluate Transaction: GetAllRecords (${identityLabel})`); - const resultBytes = await contract.evaluateTransaction('GetAllRecords'); - const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson) as MedicalRecord[]; + } + + private async newGrpcConnection(identity: FabricIdentity): Promise { + const tlsRootCert = Buffer.from(identity.tlsCertificate); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + + return new grpc.Client(identity.peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': identity.peerHostAlias, + 'grpc.keepalive_time_ms': 120000, + 'grpc.http2.min_time_between_pings_ms': 120000, + 'grpc.keepalive_timeout_ms': 20000, + 'grpc.http2.max_pings_without_data': 0, + 'grpc.keepalive_permit_without_calls': 1, + }); + } + + private createIdentity(identity: FabricIdentity): Identity { + return { + mspId: identity.mspId, + credentials: Buffer.from(identity.certificate), + }; + } + + private createSigner(identity: FabricIdentity): Signer { + const privateKey = crypto.createPrivateKey(identity.privateKey); + return signers.newPrivateKeySigner(privateKey); + } + + public async initLedger(clinicId: string, backupData: MedicalRecord[] = []): Promise { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: InitLedger (clinic: ${clinicId}, records: ${backupData.length})`); + await contract.submitTransaction('InitLedger', JSON.stringify(backupData)); + console.log('*** InitLedger transaction committed successfully'); + } + + public async storeRecordKey(clinicId: string, patientId: string, recordId: string, encryptedDEK: string): Promise { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: StoreRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + await contract.submit('StoreRecordKey', { + arguments: [patientId, recordId], + transientData: { encryptedDEK: Buffer.from(encryptedDEK) }, + endorsingOrganizations: [identity.mspId], + }); + } + + public async getRecordKey(clinicId: string, patientId: string, recordId: string): Promise { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + const resultBytes = await contract.evaluate('GetRecordKey', { + arguments: [patientId, recordId], + endorsingOrganizations: [identity.mspId], + }); + return this.utf8Decoder.decode(resultBytes); + } + + public async recordKeyExists(clinicId: string, patientId: string, recordId: string): Promise { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: RecordKeyExists (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + const resultBytes = await contract.evaluateTransaction('RecordKeyExists', patientId, recordId); + return this.utf8Decoder.decode(resultBytes) === 'true'; + } + + public async getAllRecords(clinicId: string): Promise { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetAllRecords (clinic: ${clinicId})`); + const resultBytes = await contract.evaluateTransaction('GetAllRecords'); + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson) as MedicalRecord[]; + } + + public async addRecord(clinicId: string, payload: MedicalRecord): Promise { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: AddRecord (clinic: ${clinicId})`); + + await contract.submit('AddRecord', { + arguments: [payload.patientId, payload.recordId, payload.doctorId, payload.type], + transientData: { + ipfsCidKey: Buffer.from(payload.ipfsCidKey), + }, + endorsingOrganizations: [identity.mspId], + }); + } + + public async getRecordsByPatient(clinicId: string, patientId: string): Promise { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetRecordsByPatient (clinic: ${clinicId}, patient: ${patientId})`); + + try { + // Evaluate on owner's peers if the caller is not the owner; we pass endorsingOrganizations + // as the caller's own MSP so the peer can reach into its implicit private data collection. + const resultBytes = await contract.evaluate('GetRecordsByPatient', { + arguments: [patientId], + endorsingOrganizations: [identity.mspId], + }); + + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson) as MedicalRecord[]; + } catch (err: any) { + const msg = err?.message || String(err); + if (msg.toLowerCase().includes('not authorized')) { + throw new HttpException(403, `Access denied for clinic ${clinicId} to records of patient ${patientId}`, msg); + } + throw err; } - - public async addRecord(identityLabel: string, payload: MedicalRecord): Promise { - const { contract, identity } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Submit Transaction: AddRecord (${identityLabel})`); - - await contract.submit('AddRecord', { - arguments: [ - payload.patientId, - payload.firstName, - payload.lastName, - payload.dateOfBirth, - payload.gender, - payload.bloodType, - payload.summary || '', - ], - transientData: { - ipfsCid: Buffer.from(payload.ipfsCid) - }, - endorsingOrganizations: [identity.mspId], - }); + } + + public async grantAccess(clinicId: string, patientId: string, targetClinic: string): Promise { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: GrantAccess (clinic: ${clinicId}, patient: ${patientId})`); + const targetMsp = (await identityStorage.getIdentity(targetClinic)).mspId; + await contract.submitTransaction('GrantAccess', patientId, targetMsp); + } + + public async updateRecord(clinicId: string, patientId: string, payload: Omit): Promise { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: UpdateRecord (clinic: ${clinicId})`); + + const transientData: Record = {}; + if (payload.ipfsCidKey) { + transientData.ipfsCidKey = Buffer.from(payload.ipfsCidKey); } - public async getRecordByPatientId(identityLabel: string, patientId: string): Promise { - const { contract, identity } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Evaluate Transaction: GetRecord (${identityLabel})`); - - // First, fetch the public metadata so we can determine the Owner MSP for this record. - // We don't have a separate "GetRecordPublic" chaincode function, so reuse GetAllRecords - // and find the single entry. For large datasets consider adding a light-weight metadata accessor. - const allBytes = await contract.evaluateTransaction('GetAllRecords'); - const allJson = this.utf8Decoder.decode(allBytes); - const allRecords = JSON.parse(allJson) as MedicalRecord[]; - - const publicRecord = allRecords.find(r => r.patientId === patientId); - if (!publicRecord) { - throw new HttpException(404, `Record not found: ${patientId}`); - } - - const ownerMsp = publicRecord.ownerMsp || publicRecord.ownerMsp?.toString(); - - // If caller is the owner, perform a normal evaluate (owner peer will have private data). - // If caller is NOT the owner, we must ensure the proposal is evaluated on the owner's peers - // so they can read their implicit private data collection. We instruct the gateway to target - // the owner's organizations for endorsement. - const callerMsp = identity.mspId; - - try { - if (callerMsp === ownerMsp) { - const resultBytes = await contract.evaluateTransaction('GetRecord', patientId); - const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson) as MedicalRecord; - } - - // Non-owner: request evaluation targeted at owner's org so that owner's peer can access private data. - const resultBytes = await contract.evaluate('GetRecord', { - arguments: [patientId], - endorsingOrganizations: [ownerMsp], - }); - - const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson) as MedicalRecord; - } catch (err: any) { - // Surface clearer error when access is denied - const msg = err?.message || String(err); - if (msg.toLowerCase().includes('not authorized') || msg.toLowerCase().includes('not authorized to access')) { - throw new HttpException(403, `Access denied for ${identityLabel} to record ${patientId}`, msg); - } - throw err; - } + await contract.submit('UpdateRecord', { + arguments: [patientId, payload.recordId, payload.doctorId, payload.type], + ...(Object.keys(transientData).length > 0 ? { transientData } : {}), + endorsingOrganizations: [identity.mspId], + }); + } + + public async deleteRecord(clinicId: string, patientId: string, recordId: string): Promise { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: DeleteRecord (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + await contract.submitTransaction('DeleteRecord', patientId, recordId); + } + + public async closeConnection(clinicId: string): Promise { + const connection = this.connections.get(clinicId); + if (connection) { + connection.gateway.close(); + connection.client.close(); + this.connections.delete(clinicId); + console.log(`Closed connection for clinic: ${clinicId}`); } + } - public async grantAccess(identityLabel: string, patientId: string, targetMsp: string): Promise { - const { contract } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Submit Transaction: GrantAccess (${identityLabel})`); - await contract.submitTransaction('GrantAccess', patientId, targetMsp); + public closeAllConnections(): void { + for (const [label, connection] of this.connections) { + connection.gateway.close(); + connection.client.close(); + console.log(`Closed connection for: ${label}`); } + this.connections.clear(); - - public async updateRecord( - identityLabel: string, - patientId: string, - payload: Omit - ): Promise { - const { contract, identity } = await this.getGatewayConnection(identityLabel); - console.log(`\n--> Submit Transaction: UpdateRecord (${identityLabel})`); - - await contract.submit('UpdateRecord', { - arguments: [ - patientId, - payload.firstName, - payload.lastName, - payload.dateOfBirth, - payload.gender, - payload.bloodType, - payload.summary || '', - ], - transientData: { - ipfsCid: Buffer.from(payload.ipfsCid) - }, - endorsingOrganizations: [identity.mspId], - }); + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = null; } + } - public async closeConnection(identityLabel: string): Promise { - const connection = this.connections.get(identityLabel); - if (connection) { - connection.gateway.close(); - connection.client.close(); - this.connections.delete(identityLabel); - console.log(`Closed connection for: ${identityLabel}`); - } - } + private startCleanupInterval(): void { + this.cleanupInterval = setInterval( + () => { + const now = new Date().getTime(); - public closeAllConnections(): void { for (const [label, connection] of this.connections) { + const age = now - connection.lastUsed.getTime(); + if (age > this.CONNECTION_TTL_MS) { connection.gateway.close(); connection.client.close(); - console.log(`Closed connection for: ${label}`); - } - this.connections.clear(); - - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval); - this.cleanupInterval = null; + this.connections.delete(label); + console.log(`Cleaned up stale connection for: ${label}`); + } } - } - - - private startCleanupInterval(): void { - this.cleanupInterval = setInterval(() => { - const now = new Date().getTime(); - - for (const [label, connection] of this.connections) { - const age = now - connection.lastUsed.getTime(); - if (age > this.CONNECTION_TTL_MS) { - connection.gateway.close(); - connection.client.close(); - this.connections.delete(label); - console.log(`Cleaned up stale connection for: ${label}`); - } - } - }, 5 * 60 * 1000); // Check every 5 minutes - } - - - public getConnectionStats(): { total: number; connections: Array<{ label: string; lastUsed: string }> } { - return { - total: this.connections.size, - connections: Array.from(this.connections.entries()).map(([label, conn]) => ({ - label, - lastUsed: conn.lastUsed.toISOString(), - })), - }; - } + }, + 5 * 60 * 1000, + ); // Check every 5 minutes + } + + public getConnectionStats(): { total: number; connections: Array<{ clinicId: string; lastUsed: string }> } { + return { + total: this.connections.size, + connections: Array.from(this.connections.entries()).map(([clinicId, conn]) => ({ + clinicId, + lastUsed: conn.lastUsed.toISOString(), + })), + }; + } } -export default FabricService; \ No newline at end of file +export default FabricService; diff --git a/src/services/identity-storage.service.ts b/src/services/identity-storage.service.ts index 1de36ec..4060fd3 100644 --- a/src/services/identity-storage.service.ts +++ b/src/services/identity-storage.service.ts @@ -7,8 +7,6 @@ import { HttpException } from '@/exceptions/HttpException'; class IdentityStorageService { private readonly storagePath: string; private readonly encryptionKey: Buffer; - private identities: Map = new Map(); - private initialized: boolean = false; constructor() { this.storagePath = process.env.FABRIC_IDENTITY_STORAGE_PATH || @@ -23,42 +21,32 @@ class IdentityStorageService { } } - public async initialize(): Promise { - if (this.initialized) return; - + private async readAll(): Promise { try { const dir = path.dirname(this.storagePath); await fs.mkdir(dir, { recursive: true }); - const data = await fs.readFile(this.storagePath, 'utf-8'); const stored = JSON.parse(data) as FabricIdentity[]; - - for (const identity of stored) { - identity.privateKey = this.decrypt(identity.privateKey); - this.identities.set(identity.label, identity); - } - - console.log(`Loaded ${this.identities.size} Fabric identities from storage`); + return stored.map(identity => ({ + ...identity, + privateKey: this.decrypt(identity.privateKey), + })); } catch (error: any) { if (error.code === 'ENOENT') { - console.log('No existing identity storage found. Starting fresh.'); - } else { - console.error('Error loading identity storage:', error.message); + return []; } + throw error; } - - this.initialized = true; } public async storeIdentity(input: FabricIdentityInput): Promise { - await this.initialize(); - + const identities = await this.readAll(); const now = new Date().toISOString(); - const existing = this.identities.get(input.label); + const existing = identities.find(id => id.clinicId === input.clinicId); const identity: FabricIdentity = { - label: input.label, + clinicId: input.clinicId, mspId: input.mspId, certificate: input.certificate, privateKey: input.privateKey, @@ -66,40 +54,39 @@ class IdentityStorageService { peerHostAlias: input.peerHostAlias, tlsCertificate: input.tlsCertificate, channelName: input.channelName || 'mychannel', - chaincodeName: input.chaincodeName || 'emr', + chaincodeName: input.chaincodeName || 'test', createdAt: existing?.createdAt || now, updatedAt: now, }; - this.validateIdentity(identity); - this.identities.set(identity.label, identity); - await this.persistToStorage(); + const updated = identities.filter(id => id.clinicId !== identity.clinicId); + updated.push(identity); + await this.persistToStorage(updated); - console.log(`✅ Stored identity: ${identity.label} (MSP: ${identity.mspId})`); - + console.log(`✅ Stored identity for clinic: ${identity.clinicId} (MSP: ${identity.mspId})`); return this.sanitizeIdentity(identity); } + + public async getIdentity(clinicId: string): Promise { + const identities = await this.readAll(); + const identity = identities.find(id => id.clinicId === clinicId); - public async getIdentity(label: string): Promise { - await this.initialize(); - - const identity = this.identities.get(label); if (!identity) { - throw new HttpException(404, `Identity not found: ${label}`); + throw new HttpException(404, `Identity not found for clinic: ${clinicId}`); } return identity; } public async listIdentities(): Promise>> { - await this.initialize(); + const identities = await this.readAll(); - return Array.from(this.identities.values()).map(identity => ({ - label: identity.label, + return identities.map(identity => ({ + clinicId: identity.clinicId, mspId: identity.mspId, peerEndpoint: identity.peerEndpoint, peerHostAlias: identity.peerHostAlias, @@ -110,28 +97,29 @@ class IdentityStorageService { })); } - public async deleteIdentity(label: string): Promise { - await this.initialize(); + public async deleteIdentity(clinicId: string): Promise { + const identities = await this.readAll(); + const index = identities.findIndex(id => id.clinicId === clinicId); - if (!this.identities.has(label)) { - throw new HttpException(404, `Identity not found: ${label}`); + if (index === -1) { + throw new HttpException(404, `Identity not found for clinic: ${clinicId}`); } - this.identities.delete(label); - await this.persistToStorage(); + identities.splice(index, 1); + await this.persistToStorage(identities); - console.log(`🗑️ Deleted identity: ${label}`); + console.log(`🗑️ Deleted identity for clinic: ${clinicId}`); } - public async hasIdentity(label: string): Promise { - await this.initialize(); - return this.identities.has(label); + public async hasIdentity(clinicId: string): Promise { + const identities = await this.readAll(); + return identities.some(id => id.clinicId === clinicId); } private validateIdentity(identity: FabricIdentity): void { - if (!identity.label || identity.label.trim() === '') { - throw new HttpException(400, 'Identity label is required'); + if (!identity.clinicId || identity.clinicId.trim() === '') { + throw new HttpException(400, 'Clinic ID is required'); } if (!identity.mspId || identity.mspId.trim() === '') { @@ -156,10 +144,9 @@ class IdentityStorageService { } - private async persistToStorage(): Promise { - const toStore = Array.from(this.identities.values()).map(identity => ({ + private async persistToStorage(identities: FabricIdentity[]): Promise { + const toStore = identities.map(identity => ({ ...identity, - privateKey: this.encrypt(identity.privateKey), })); diff --git a/src/services/ipfs.service.ts b/src/services/ipfs.service.ts index 638b627..052f373 100644 --- a/src/services/ipfs.service.ts +++ b/src/services/ipfs.service.ts @@ -40,6 +40,15 @@ export class IpfsService { } } + public async checkHealth(): Promise<{ status: string; message: string }> { + try { + await this.pinata.testAuthentication(); + return { status: 'ok', message: 'IPFS connection is healthy' }; + } catch (e) { + throw new HttpException(503, `IPFS connection failed: ${e.message}`); + } + } + public async deleteFile(cid: string): Promise { try { await this.pinata.files.delete([cid]); diff --git a/src/services/key-management.service.ts b/src/services/key-management.service.ts index a00bfe1..af48c05 100644 --- a/src/services/key-management.service.ts +++ b/src/services/key-management.service.ts @@ -1,51 +1,45 @@ import { Service } from 'typedi'; -import prisma from '@/config/prisma'; import { EncryptionService } from './encryption.service'; import { HttpException } from '@/exceptions/HttpException'; -import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import FabricService from '@/services/fabric.service'; @Service() export class KeyManagementService { private encryptionService = new EncryptionService(); - - public async createPatientKey(patientId: string): Promise { - const existing = await prisma.encryptionKey.findUnique({ - where: { - patient_id: patientId - } - }); - if (existing){ - const error = createBilingualError(400, ErrorMessages.PATIENT_KEY_ALREADY_EXISTS); - throw new HttpException(error.status, error.message, error.messageAr); + private fabricService = new FabricService(); + + /** + * Generates a fresh DEK, wraps it with the master key, and stores the + * encrypted form in the record's implicit private data collection on the + * blockchain. Throws if a key already exists for this record. + */ + public async createRecordKey(clinicId: string, patientId: string, recordId: string): Promise { + const exists = await this.fabricService.recordKeyExists(clinicId, patientId, recordId); + if (exists) { + throw new HttpException(400, `Encryption key already exists for record: ${recordId}`); } - const patientDEK = this.encryptionService.generateDEK(); - const encryptedDEK = this.encryptionService.encryptDEK(patientDEK); - - await prisma.encryptionKey.create({ - data: { - patient_id: patientId, - encrypted_key: encryptedDEK, - algorithm: 'AES-256-GCM', - } - }); - patientDEK.fill(0); + const recordDEK = this.encryptionService.generateDEK(); + const encryptedDEK = this.encryptionService.encryptDEK(recordDEK); + recordDEK.fill(0); + + await this.fabricService.storeRecordKey(clinicId, patientId, recordId, encryptedDEK); } - public async getPatientDEK(patientId: string): Promise { - const keyRecord = await prisma.encryptionKey.findUnique({ - where: { - patient_id: patientId - } - }); - - if (!keyRecord) { - await this.createPatientKey(patientId); - // const error = createBilingualError(404, ErrorMessages.PATIENT_KEY_NOT_FOUND); - // throw new HttpException(error.status, error.message, error.messageAr); + /** + * Fetches the encrypted DEK for a specific record from the blockchain and + * decrypts it with the master key. Creates a new key automatically if one + * does not yet exist. + */ + public async getRecordDEK(clinicId: string, patientId: string, recordId: string): Promise { + const exists = await this.fabricService.recordKeyExists(clinicId, patientId, recordId); + if (!exists) { + await this.createRecordKey(clinicId, patientId, recordId); } - return this.encryptionService.decryptDEK(keyRecord.encrypted_key); - } + const encryptedDEK = await this.fabricService.getRecordKey(clinicId, patientId, recordId); + return this.encryptionService.decryptDEK(encryptedDEK); + } } + diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index d7d3d93..8f5183b 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -2,11 +2,14 @@ import { HttpException } from '@/exceptions/HttpException'; import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { MedicalRecord, MedicalRecordFile } from '@/interfaces/medicalRecords.interface'; import prisma from '@/config/prisma'; +import { Prisma } from '@prisma/client'; import { Service } from 'typedi'; import { IpfsService } from '@/services/ipfs.service'; import { EncryptionService } from '@/services/encryption.service'; import { KeyManagementService } from '@/services/key-management.service'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; +import { randomUUID } from 'crypto'; +import FabricService from '@/services/fabric.service'; @Service() @@ -15,8 +18,10 @@ export class MedicalRecordService { private ipfsService = new IpfsService(); private encryptionService = new EncryptionService(); private keyManagementService = new KeyManagementService(); + private fabricService = new FabricService(); public async createMedicalRecord( + clinicId: string, patientId: string, doctorId: string, fileData: CreateMedicalRecordDto, @@ -24,35 +29,36 @@ export class MedicalRecordService { fileName: string, mimeType: string, ): Promise { - const patientDEK = await this.keyManagementService.getPatientDEK(patientId); - const encryptedFile = this.encryptionService.encryptFile(fileBuffer, patientDEK); - patientDEK.fill(0); + const recordId = randomUUID(); - const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(fileBuffer, recordDEK); + recordDEK.fill(0); - const keyRecord = await prisma.encryptionKey.findUnique({ - where: { - patient_id: patientId - }, - select: { - id: true - }, - }); + const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); await prisma.medicalRecord.create({ data: { + id: recordId, patient_id: patientId, doctor_id: doctorId, - clinic_id: (fileData as any).clinicId, + clinic_id: clinicId, appointment_id: (fileData as any).appointmentId, name: fileData.name, cid: cid, type: fileData.type, mime_type: mimeType, - key_id: keyRecord.id, - }, + } as Prisma.MedicalRecordUncheckedCreateInput, }); + // Sync to blockchain ledger + await this.fabricService.addRecord(clinicId, { + patientId, + recordId, + doctorId, + type: fileData.type, + ipfsCidKey: cid, + }); } public async getRecordFile(recordId: string): Promise { @@ -81,9 +87,9 @@ export class MedicalRecordService { const encryptedFile = await this.ipfsService.getFile(record.cid); - const patientDEK = await this.keyManagementService.getPatientDEK(record.patient_id); - const decryptedFile = this.encryptionService.decryptFile(encryptedFile, patientDEK); - patientDEK.fill(0); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); return { id: record.id, @@ -99,6 +105,10 @@ export class MedicalRecordService { }; } + public async checkIpfsHealth(): Promise<{ status: string; message: string }> { + return this.ipfsService.checkHealth(); + } + public async getPatientFiles(patientId: string): Promise { const records = await prisma.medicalRecord.findMany({ where: { @@ -153,13 +163,13 @@ export class MedicalRecordService { throw new HttpException(error.status, error.message, error.messageAr); } + // Remove from blockchain ledger + await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, recordId); + + // Soft-delete in DB await prisma.medicalRecord.update({ - where: { - id: recordId - }, - data: { - deleted_at: new Date() - }, + where: { id: recordId }, + data: { deleted_at: new Date() }, }); } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index a344eed..624b265 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -8552,6 +8552,284 @@ } } } + }, + "/record/health/ipfs": { + "get": { + "tags": [ + "Medical Records" + ], + "description": "Checks connectivity to the IPFS (Pinata) service", + "responses": { + "200": { + "description": "IPFS connection is healthy", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + }, + "message": { + "type": "string", + "example": "IPFS connection is healthy" + } + }, + "xml": { + "name": "main" + } + } + }, + "503": { + "description": "IPFS service is unreachable" + } + } + } + }, + "/record/patient": { + "get": { + "tags": [ + "Medical Records" + ], + "description": "Retrieves all medical record metadata for the authenticated patient (no file bytes)", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Medical records retrieved successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical records retrieved successfully" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "patient_id": { + "type": "string", + "example": "uuid-string" + }, + "clinic_id": { + "type": "string", + "example": "uuid-string" + }, + "doctor_id": { + "type": "string", + "example": "uuid-string" + }, + "appointment_id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Blood Test Results" + }, + "type": { + "type": "string", + "example": "LAB_RESULT" + }, + "mime_type": { + "type": "string", + "example": "application/pdf" + }, + "cid": { + "type": "string", + "example": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + } + } + } + }, + "/record/{id}": { + "get": { + "tags": [ + "Medical Records" + ], + "description": "Downloads and decrypts a single medical record file. Returns raw file bytes with appropriate Content-Type header.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the medical record" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Decrypted file bytes streamed back with Content-Type, Content-Disposition, x-record-id, x-patient-id, x-record-type headers set" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Record not found or already deleted" + } + } + }, + "delete": { + "tags": [ + "Medical Records" + ], + "description": "Soft-deletes a medical record (sets deleted_at). File remains on IPFS but is inaccessible via the API.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the medical record to delete" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Medical record deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical record deleted successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Record not found or already deleted" + } + } + } + }, + "/record/{doctorId}/upload": { + "post": { + "tags": [ + "Medical Records" + ], + "description": "Patient uploads a new medical record file", + "parameters": [ + { + "name": "doctorId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the doctor associated with this record" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "The medical record file", + "required": true, + "type": "file" + }, + { + "name": "name", + "in": "formData", + "description": "Display name for the record", + "required": true, + "type": "string" + }, + { + "name": "type", + "in": "formData", + "description": "Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)", + "required": true, + "type": "string" + }, + { + "name": "clinicId", + "in": "formData", + "description": "UUID of the clinic", + "required": true, + "type": "string" + }, + { + "name": "appointmentId", + "in": "formData", + "description": "UUID of the appointment (optional)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Medical record uploaded successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical record uploaded successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "No file uploaded or validation failed" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Patient encryption key not found" + } + } + } } } } \ No newline at end of file diff --git a/src/swagger.mjs b/src/swagger.mjs index 2518576..6fd47b3 100644 --- a/src/swagger.mjs +++ b/src/swagger.mjs @@ -19,12 +19,21 @@ const doc = { { name: 'Users', description: 'User account endpoints' }, { name: 'Nurses', description: 'Nurse account endpoints' }, ], - }; const outputFile = './swagger-output.json'; -const endpointsFiles = ['./routes/auth.route.ts', './routes/fabric.route.ts', './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts', './src/routes/doctors.route.ts', './src/routes/clinic.route.ts' - , './src/routes/appointment.route.ts', './src/routes/queue.route.ts', './src/routes/user.route.ts', './src/routes/nurse.route.ts']; +const endpointsFiles = [ + './routes/auth.route.ts', + './routes/fabric.route.ts', + './src/routes/admin.route.ts', + './src/routes/superAdmin.route.ts', + './src/routes/doctors.route.ts', + './src/routes/clinic.route.ts', + './src/routes/appointment.route.ts', + './src/routes/queue.route.ts', + './src/routes/user.route.ts', + './src/routes/nurse.route.ts', + './src/routes/medical-record.route.ts' +]; -swaggerAutogen()(outputFile, endpointsFiles, doc); \ No newline at end of file +swaggerAutogen()(outputFile, endpointsFiles, doc); From 2034e28de0b1af7e6a9e423ff08ae1c9d41ae79a Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Sat, 7 Mar 2026 11:26:19 +0200 Subject: [PATCH 186/210] refactor: update generateSOAP method to return any type and ensure async handling in aiAppointmentsController test: add mixed and separate audio processing tests feat: implement clinical audio generation using Google Cloud TTS --- src/controllers/ai_appointments.controller.ts | 3 +- src/services/ai_appointments.service.ts | 6 +- src/test/BADspeechSynthesizer.js | 105 ++++++++++++++++++ .../{mixedAudioAI.test.js => audioAI.test.js} | 0 4 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 src/test/BADspeechSynthesizer.js rename src/test/{mixedAudioAI.test.js => audioAI.test.js} (100%) diff --git a/src/controllers/ai_appointments.controller.ts b/src/controllers/ai_appointments.controller.ts index 2532e45..7f27cb0 100644 --- a/src/controllers/ai_appointments.controller.ts +++ b/src/controllers/ai_appointments.controller.ts @@ -60,8 +60,7 @@ export class AiAppointmentsController { const error = createBilingualError(400, ErrorMessages.MISSING_AUDIO_KEYS); throw new HttpException(error.status, error.message, error.messageAr); } - const SOAP = this.aiAppointmentsService.generateSOAP(finalScript); - + const SOAP = await this.aiAppointmentsService.generateSOAP(finalScript); const responseMessage = createMultiLangMessage(SuccessResponseMessages.SOAP_GENERATED); res.status(202).json({ ...responseMessage, diff --git a/src/services/ai_appointments.service.ts b/src/services/ai_appointments.service.ts index 54be8a5..211374b 100644 --- a/src/services/ai_appointments.service.ts +++ b/src/services/ai_appointments.service.ts @@ -57,7 +57,7 @@ export class AiAppointmentsService { return finalScript; } - public async generateSOAP(finalScript: string): Promise { + public async generateSOAP(finalScript: string): Promise { const chatCompletion = await this.groq.chat.completions.create({ messages: [ { @@ -88,10 +88,10 @@ CLINICAL GUIDELINES: // Extract the JSON string from the LLM response const jsonString = chatCompletion.choices[0]?.message?.content; - + // Parse it into a native JavaScript object const soapNote = JSON.parse(jsonString); - + return soapNote; } diff --git a/src/test/BADspeechSynthesizer.js b/src/test/BADspeechSynthesizer.js new file mode 100644 index 0000000..1cbdb05 --- /dev/null +++ b/src/test/BADspeechSynthesizer.js @@ -0,0 +1,105 @@ +import textToSpeech from '@google-cloud/text-to-speech'; +import fs from 'fs'; +import util from 'util'; + +async function generateClinicalAudio() { + // Initialize the Google Cloud TTS client + const client = new textToSpeech.TextToSpeechClient(); + + // The text to synthesize + const fullTranscript = ` + Doctor: أهلاً بك يا أستاذة منى، اتفضلي استريحي. قوليلي، إيه اللي بيشتكي منه النهاردة؟ +Patient: أهلاً بيك يا دكتور. والله أنا بقالي فترة تعبانة جداً، مفاصلي كلها بتوجعني ومش قادرة أمارس حياتي الطبيعية خالص. +Doctor: ألف سلامة عليكي. طيب خلينا ناخد الموضوع واحدة واحدة. الـ joint pain ده أو وجع المفاصل بدأ معاكي من إمتى بالظبط؟ +Patient: يعني تقريباً من حوالي تلات أو أربع شهور كده. في الأول كان وجع خفيف وبيروح، بس بقاله شهرين زايد أوي وما بيروحش. +Doctor: تمام. طيب الوجع ده متركز في مفاصل معينة ولا في جسمك كله؟ يعني إيه أكتر مفاصل حاسة فيها بالـ pain؟ +Patient: أكتر حاجة إيديا، صوابعي بتوجعني جداً، ورسغ إيدي، وكمان ركبي الاتنين. +Doctor: طيب بالنسبة لإيديكي، الوجع ده symmetrical؟ يعني موجود في الإيد اليمين والشمال زي بعض بالظبط؟ +Patient: أيوة بالظبط يا دكتور، الإيدين زي بعض. +Doctor: ولما بتصحي من النوم الصبح، هل بتحسي إن مفاصلك متخشبة؟ يعني فيه morning stiffness؟ +Patient: أيوة جداً! دي أكتر حاجة مضيقاني. بصحى من النوم حاسة إني متكتفة ومش قادرة أتني صوابعي خالص، ولا حتى أقدر أمسك كوباية الشاي أو أسرح شعري. +Doctor: الـ morning stiffness ده بيستمر معاكي وقت قد إيه تقريباً لحد ما تبدأي تحسي إن المفاصل فكت شوية وتقدري تستخدميها؟ بياخد أكتر من ساعة؟ +Patient: أيوة، ساعات بياخد ساعتين أو تلاتة الصبح على ما أقدر أحرك إيدي طبيعي. +Doctor: طيب، هل بتلاحظي أي تورم أو احمرار في المفاصل؟ يعني فيه swelling أو redness؟ +Patient: التورم موجود، بحس إن عقل صوابعي وارمة وتخينة كده عن الطبيعي، وساعات بحس إنها دافية شوية لما بحط إيدي عليها. +Doctor: أمم، تمام. طيب الوجع ده بيزيد مع الحركة والمجهود ولا بيزيد وإنتي مرتاحة؟ +Patient: هو بيزيد الصبح زي ما قلتلك وإنا لسة قايمة من السرير، ولما ببدأ أتحرك وأعمل شغل البيت بحس إنه بيخف شوية، بس بيرجع يتعبني تاني لو عملت مجهود زيادة. +Doctor: عظيم جداً، ده بيسموه inflammatory pain pattern. طيب هل فيه أي أعراض تانية بره المفاصل؟ يعني مثلاً حاسة بـ fatigue، إرهاق عام، أو سخونية low-grade fever؟ +Patient: الإرهاق ده فظيع، أنا دايماً حاسة إني مهدودة ومافيش طاقة. وساعات فعلاً بحس إن جسمي مكسر ودافي شوية بالليل. وكمان خسيت حوالي ٤ كيلو في الشهرين اللي فاتوا من غير ما أعمل دايت. +Doctor: طيب هل بيجيلك أي skin rash، طفح جلدي في وشك أو جسمك؟ أو قرح في البق oral ulcers متكررة؟ +Patient: لا مفيش طفح جلدي، بس بيجيلي قرح في البق كل فترة كده وبتوجعني. +Doctor: هل بتحسي بـ dry eyes أو dry mouth؟ يعني عينيكي أو ريقك بينشفوا بصورة ملحوظة؟ +Patient: عيني بتنشف شوية وبحس فيها بزي رمل كده ساعات، وبضطر أشرب مية كتير عشان ريقي بينشف. +Doctor: هل شعرك بيقع بشكل غير طبيعي؟ Hair loss؟ +Patient: بيقع شوية بس عادي يعني، مش لدرجة إني أصلع. +Doctor: في أي وجع في الصدر أو كرشة نفس لما بتاخدي نفس عميق؟ +Patient: لا الحمد لله، مفيش الكلام ده. +Doctor: هل في حد في العيلة عنده أي autoimmune diseases؟ يعني أمراض مناعية زي الروماتويد، الذئبة الحمراء SLE، أو الصدفية؟ +Patient: أيوة، خالتي بتتعالج من الروماتويد بقالها سنين. +Doctor: طيب، في أي أمراض مزمنة تانية بتتعالجي منها؟ ضغط، سكر، أو أي مشاكل في القلب؟ +Patient: لا الحمد لله، ما باخدش أي أدوية غير المسكنات اليومين دول عشان الوجع. +Doctor: بتاخدي مسكنات إيه، والجرعة بتاعتها قد إيه تقريباً؟ +Patient: باخد بروفين ٤٠٠، تلات مرات في اليوم، بس مبقاش يجيب نتيجة زي الأول. + +(Doctor pauses for physical examination) + +Doctor: طيب تعالي نتفضل على السرير عشان أعملك physical examination ونفحص المفاصل دي. +Patient: حاضر يا دكتور. +Doctor: هضغط على المفاصل دي شوية، لو حسيتي بوجع قوليلي. ده بيوجع؟ +Patient: آآه، ده بيوجع أوي. +Doctor: تمام، الوجع ده في الـ MCP joints أو مفاصل الصوابع. في هنا واضح synovitis، يعني التهاب في الغشاء المبطن للمفصل. المفاصل دي وارمة وـ tender جداً. طيب نتني الرسغ كده... ده بيوجع؟ +Patient: أيوة وجع شديد هنا في الإيد اليمين أكتر. +Doctor: مظبوط، فيه swelling في الـ wrist joint. طيب نفرد الركب ونثنيها... في شوية crepitus أو طرقعة هنا، بس برضه فيه mild effusion، ارتشاح بسيط في الركبة اليمين. تقدري تقومي تقعدي على الكرسي تاني. + +(Patient sits back down) + +Doctor: بصي يا أستاذة منى، من الـ history اللي أخدته منك ومن الـ clinical examination، الأعراض اللي عندك دي زي الـ symmetrical polyarthritis والـ prolonged morning stiffness بتمشي أكتر مع مرض الروماتويد المفصلي، أو الـ Rheumatoid Arthritis. +Patient: روماتويد؟ زي خالتي؟ طب ده معناه إني مش هقدر أحرك إيدي بعد كده وهتتعوج؟ +Doctor: لا خالص، ماتقلقيش. الطب اتقدم جداً، ولو اكتشفنا الموضوع بدري وبدأنا العلاج، بنقدر نتحكم في المرض تماماً وتعيشي حياتك بشكل طبيعي جداً من غير أي deformities. بس طبعاً عشان نأكد الـ diagnosis ده، لازم نطلب شوية تحاليل وأشعة. +Patient: تحاليل إيه يا دكتور؟ +Doctor: هطلب منك مجموعة labs كاملة. أولاً هنعمل CBC عشان نطمن على صورة الدم ونشوف لو فيه anemia of chronic disease. وهنعمل دلالات التهاب زي الـ ESR والـ CRP، ودول أكيد هيطلعوا عاليين شوية. +Patient: تمام. +Doctor: والأهم بقى، هنعمل تحاليل المناعة الخاصة بالروماتويد، وهي الـ Rheumatoid Factor أو الـ RF، وتحليل تاني أدق اسمه Anti-CCP. دول بيساعدونا نأكد التشخيص بنسبة كبيرة. وكمان هطلب وظايف كبد وكلى، ALT, AST, Creatinine عشان نطمن قبل ما نبدأ أي أدوية قوية. +Patient: والأشعة دي على إيدي؟ +Doctor: بالظبط، هنعمل X-ray أو أشعة عادية على الإيدين والرسغ، وكمان على الركب. دي هتبين لنا لو فيه أي bone erosions أو تآكل في العضم، أو joint space narrowing. دي بتكون زي baseline أو خط بداية نتابع بيه حالة المفاصل بعد كده. +Patient: هعملهم وأجيبهم لحضرتك على طول. بس أنا حالياً مش قادرة أستحمل الوجع لحد ما التحاليل تطلع، مفيش حاجة تريّحني؟ +Doctor: أكيد طبعاً. إحنا هنمشي على خطتين. خطة سريعة للوجع، وخطة طويلة الأمد للمرض نفسه. دلوقتي هكتبلك NSAIDs، دي مضادات التهاب غير ستيرويدية هتاخديها بعد الأكل عشان الوجع. وهديكي كمان low-dose corticosteroids، يعني نسبة كورتيزون بسيطة جداً، زي الـ Prednisone 5 mg كل يوم الصبح. دي بنسميها bridging therapy، بتهدي الالتهاب بسرعة جداً لحد ما الأدوية الأساسية تبدأ تشتغل. +Patient: أنا بخاف من الكورتيزون أوي يا دكتور، مش ده بينفخ الجسم وبيعمل هشاشة؟ +Doctor: الجرعة دي صغيرة جداً لفترة مؤقتة، شهر أو اتنين بالكتير، ومش هتلحق تعمل الـ side effects دي. ماتقلقيش خالص. ولما التحاليل تطلع ونتأكد من التشخيص، هنبدأ في مجموعة أدوية اسمها DMARDs، أو الأدوية المعدلة لطبيعة المرض، وأشهرهم دواء اسمه Methotrexate. ده اللي بيوقف نشاط المرض وبيحمي المفاصل من التآكل. +Patient: تمام يا دكتور، أنا هعمل التحاليل دي النهاردة. +Doctor: ممتاز. أنا هسجل كل الـ clinical notes بتاعتك على السيستم عندنا، وهضيفلك الأدوية المبدئية. من خلال الـ medication tracker بتاع المستشفى، هيجيلك تنبيهات بمواعيد الأدوية عشان ما تنسيش، خصوصاً إن الأدوية المناعية بعد كده محتاجة التزام دقيق جداً. وأول ما التحاليل والأشعة يخلصوا، هتقدري ترفقيهم على السيستم والـ appointment management هيحددلك أقرب ميعاد للمتابعة معايا. +Patient: دي حاجة ممتازة والله بتسهل علينا كتير. شكراً جداً يا دكتور، طمنتني. +Doctor: العفو يا أستاذة منى، ألف سلامة عليكي، ومستني أشوفك بالتحاليل الأسبوع الجاي إن شاء الله. + `; + + // Construct the request + const request = { + input: { text: fullTranscript }, + // Using an Egyptian Arabic voice model + voice: { + languageCode: 'ar-EG', + name: 'ar-EG-Wavenet-B' // 'A' is female, 'B' is male, 'C' is male, 'D' is female + }, + // Set output format to MP3 + audioConfig: { + audioEncoding: 'MP3', + speakingRate: 1.0, // Adjust speed if needed (0.25 to 4.0) + }, + }; + + try { + console.log('Generating audio, please wait...'); + // Perform the text-to-speech request + const [response] = await client.synthesizeSpeech(request); + + // Write the binary audio content to a local file + const writeFile = util.promisify(fs.writeFile); + await writeFile('rheumatology_consultation.mp3', response.audioContent, 'binary'); + + console.log('Success! Audio saved to rheumatology_consultation.mp3'); + } catch (error) { + console.error('Error generating audio:', error); + } +} + +generateClinicalAudio(); \ No newline at end of file diff --git a/src/test/mixedAudioAI.test.js b/src/test/audioAI.test.js similarity index 100% rename from src/test/mixedAudioAI.test.js rename to src/test/audioAI.test.js From 55af6bafd484137907bf6c5748058ee985d9921b Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 7 Mar 2026 19:45:42 +0200 Subject: [PATCH 187/210] fix: doctor schedule --- src/dtos/doctors.dto.ts | 3 +++ src/routes/doctors.route.ts | 7 +++++++ src/services/appointment.service.ts | 12 +++++++----- src/services/doctor.service.ts | 1 + src/swagger-output.json | 13 +++++++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/dtos/doctors.dto.ts b/src/dtos/doctors.dto.ts index 442f31d..d8e0e58 100644 --- a/src/dtos/doctors.dto.ts +++ b/src/dtos/doctors.dto.ts @@ -21,6 +21,9 @@ export class DoctorSignupRequestDto { @IsNotEmpty() public password: string; + @IsString() + availability_type?: AvailabilityType; + @IsString() @IsNotEmpty() public gender: Gender; diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 8c092ba..1e4010a 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -56,6 +56,13 @@ export class DoctorsRoute implements Routes { type: 'string', enum: ['MALE', 'FEMALE'] } + #swagger.parameters['availability_type'] = { + in: 'formData', + description: 'availability type of the doctor', + required: false, + type: 'string', + enum: ['UNSET', 'ONLINE', 'OFFLINE', 'BOTH'] + } #swagger.parameters['date_of_birth'] = { in: 'formData', description: 'Doctor date of birth (YYYY-MM-DD)', diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 4f64def..8576ad3 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -57,7 +57,7 @@ export class AppointmentService { const today = new Date(); today.setUTCHours(0, 0, 0, 0); - for (let i = 1; i <= daysAhead; i++) { + for (let i = 0; i < daysAhead; i++) { // create a copy from today --> if we used today directly it will be modified to today + 1 --> tomorrow date const currentDate = new Date(today); currentDate.setUTCDate(today.getUTCDate() + i); // current day now is = today + 1 @@ -113,8 +113,8 @@ export class AppointmentService { const requestedDate = new Date(date); const dayOfWeek = this.getDayOfWeek(requestedDate.getUTCDay()); - const today = new Date(); - today.setUTCHours(0, 0, 0, 0); + const now = new Date(); + const { start: today, end: endOfToday } = this.getTodayBoundaries(now); const requestedDateOnly = new Date(requestedDate); requestedDateOnly.setUTCHours(0, 0, 0, 0); @@ -196,8 +196,10 @@ export class AppointmentService { return this.doesSlotOverlap(slotStart, slotEnd, apptStart, apptEnd); }); - const now = new Date(); - const isInPast = slotEnd <= now; + const nowUTC = new Date(); + const egyptOffset = 2 * 60 * 60 * 1000; + const now = new Date(nowUTC.getTime() + egyptOffset); + const isInPast = slotStart <= now; return { start: slot.start, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 46301ae..48756bc 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -72,6 +72,7 @@ export class DoctorService { id: createdUser.id, specialization: "IMMUNOLOGY", account_status: DoctorAccountStatus.PENDING, + availability_type: doctorData.availability_type, }, }); return createdUser.id; diff --git a/src/swagger-output.json b/src/swagger-output.json index a344eed..0ba9261 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4003,6 +4003,19 @@ "FEMALE" ] }, + { + "name": "availability_type", + "in": "formData", + "description": "availability type of the doctor", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "ONLINE", + "OFFLINE", + "BOTH" + ] + }, { "name": "date_of_birth", "in": "formData", From 48a59316deae94f3e2ea08febb536e75f9712713 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sat, 7 Mar 2026 20:24:54 +0200 Subject: [PATCH 188/210] add get working nurse route/ update queue calc --- src/controllers/doctor.controller.ts | 15 +++++ src/routes/doctors.route.ts | 42 +++++++++++++ src/services/doctor.service.ts | 48 ++++++++++++++ src/services/queue.service.ts | 11 ++-- src/swagger-output.json | 93 ++++++++++++++++++++++++++++ src/utils/responseMessages.ts | 4 ++ 6 files changed, 209 insertions(+), 4 deletions(-) diff --git a/src/controllers/doctor.controller.ts b/src/controllers/doctor.controller.ts index e97117e..cc64cb6 100644 --- a/src/controllers/doctor.controller.ts +++ b/src/controllers/doctor.controller.ts @@ -114,6 +114,21 @@ export class DoctorController { }); } + public getWorkingNurses = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { + const doctorId = req.user?.id; + if (!doctorId) { + const error = createBilingualError(401, ErrorMessages.DOCTOR_ID_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + const nurses = await this.doctorService.getWorkingNurses(doctorId); + + const responseMessage = createMultiLangMessage(SuccessResponseMessages.NURSES_RETRIEVED_SUCCESSFULLY); + res.status(200).json({ + data: nurses, + ...responseMessage + }); + } + public approveApplicant = async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const doctorId = req.user?.id; const { applicantId } = req.params; diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 1e4010a..7dd7acb 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -362,6 +362,48 @@ export class DoctorsRoute implements Routes { AuthMiddleware, this.doctorsController.getAnnouncementApplicants ) + this.router.get( + `/doctors/nurses`, + /* + #swagger.path = '/doctors/nurses' + #swagger.method = 'get' + #swagger.tags = ['Doctors'] + #swagger.description = 'Retrieves all nurses working with the doctor' + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + #swagger.responses[200] = { + description: 'Nurses retrieved successfully', + schema: { + data: [ + { + id: 'uuid-string', + name: 'Max Mustermann', + email: 'max.mustermann@example.com', + gender: 'FEMALE', + phone: '+201234567890', + age: 28, + profilePic: 'https://res.cloudinary.com/example/photo.jpg', + years_of_experience: 5, + nationalCardUrl: 'https://res.cloudinary.com/example/national_card.pdf', + brief: 'Experienced ICU nurse with 5 years in critical care', + bonusFileUrl: 'https://res.cloudinary.com/example/bonus.pdf' + } + ], + messageEn: 'Nurses retrieved successfully', + messageAr: 'تم استرجاع الممرضين بنجاح' + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + */ + AuthMiddleware, + this.doctorsController.getWorkingNurses + ) this.router.patch( `/doctors/announcements/:applicantId/approve`, diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index 48756bc..d7b2e10 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -775,6 +775,54 @@ export class DoctorService { }))); } + public async getWorkingNurses(doctorId: string): Promise { + const nurses = await prisma.nurseSchedule.findMany({ + where: { + doctor_id: doctorId, + deleted_at: null + }, + select: { + nurse: { + select: { + years_of_experience: true, + nationalCardUrl: true, + bonusFileUrl: true, + brief: true, + user: { + select: { + id: true, + name: true, + email: true, + gender: true, + phone: true, + date_of_birth: true, + photo_url: true, + } + } + } + } + } + }); + + const uniqueNurses = Array.from( + new Map(nurses.map(({ nurse }) => [nurse.user.id, nurse])).values() + ); + + return Promise.all(uniqueNurses.map(async (nurse) => ({ + id: nurse.user.id, + name: nurse.user.name, + email: nurse.user.email, + gender: nurse.user.gender, + phone: nurse.user.phone, + age: await this.userService.calculateUserAge(nurse.user.date_of_birth), + profilePic: nurse.user.photo_url, + years_of_experience: nurse.years_of_experience, + nationalCardUrl: nurse.nationalCardUrl, + bonusFileUrl: nurse.bonusFileUrl, + brief: nurse.brief, + }))); + } + public async postAnnouncement(doctorId: string, data: PostAnnouncementDto): Promise { const doctor = await prisma.doctor.findUnique({ where: { diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 009eeb5..97b817c 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -107,13 +107,16 @@ export class QueueService { throw new HttpException(error.status, error.message, error.messageAr); } - const appointmentsAhead = todayAppointments.slice(0, currentIdx).filter(app => app.status === 'CONFIRMED'); - - const patientsAhead = appointmentsAhead.length; + const patientsAhead = todayAppointments.slice(0, currentIdx).filter(app => app.status === 'CONFIRMED').length; const position = currentIdx + 1; // NOOTEEE --> now time - scheduled time but in mins - const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); + const nowUTC = new Date(); + const egyptOffset = 2 * 60 * 60 * 1000; + const now = new Date(nowUTC.getTime() + egyptOffset); + + // const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); + const estimatedWaitMinutes = Math.max(0, Math.round((appointment.scheduled_time.getTime() - now.getTime()) / (1000 * 60))); this.updateQueueParameters(appointmentId, position, patientsAhead, estimatedWaitMinutes); } diff --git a/src/swagger-output.json b/src/swagger-output.json index 0ba9261..38b7c87 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4587,6 +4587,99 @@ } } }, + "/doctors/nurses": { + "get": { + "tags": [ + "Doctors" + ], + "description": "Retrieves all nurses working with the doctor", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Nurses retrieved successfully", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Max Mustermann" + }, + "email": { + "type": "string", + "example": "max.mustermann@example.com" + }, + "gender": { + "type": "string", + "example": "FEMALE" + }, + "phone": { + "type": "string", + "example": "+201234567890" + }, + "age": { + "type": "number", + "example": 28 + }, + "profilePic": { + "type": "string", + "example": "https://res.cloudinary.com/example/photo.jpg" + }, + "years_of_experience": { + "type": "number", + "example": 5 + }, + "nationalCardUrl": { + "type": "string", + "example": "https://res.cloudinary.com/example/national_card.pdf" + }, + "brief": { + "type": "string", + "example": "Experienced ICU nurse with 5 years in critical care" + }, + "bonusFileUrl": { + "type": "string", + "example": "https://res.cloudinary.com/example/bonus.pdf" + } + } + } + }, + "messageEn": { + "type": "string", + "example": "Nurses retrieved successfully" + }, + "messageAr": { + "type": "string", + "example": "تم استرجاع الممرضين بنجاح" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + } + } + } + }, "/doctors/announcements/{applicantId}/approve": { "patch": { "tags": [ diff --git a/src/utils/responseMessages.ts b/src/utils/responseMessages.ts index 235c023..dcfebd1 100644 --- a/src/utils/responseMessages.ts +++ b/src/utils/responseMessages.ts @@ -137,6 +137,10 @@ export const SuccessResponseMessages = { message_en: "Announcement applicants retrieved successfully.", message_ar: "تم استرجاع المتقدمين للإعلان بنجاح.", }, + NURSES_RETRIEVED_SUCCESSFULLY: { + message_en: "Nurses retrieved successfully.", + message_ar: "تم استرجاع الممرضين بنجاح.", + }, ANNOUNCEMENTS_RETRIEVED: { message_en: "Announcements retrieved successfully.", message_ar: "تم استرجاع الإعلانات بنجاح.", From 751d6bd4000620fc3a8599b4782c4ac90ed3c014 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sun, 8 Mar 2026 00:32:57 +0200 Subject: [PATCH 189/210] feat: enhance medical record management with clinic-based access and new endpoints --- src/controllers/medical-records.controller.ts | 63 ++- src/routes/fabric.route.ts | 2 - src/routes/medical-record.route.ts | 188 +++++++- src/services/medical-records.service.ts | 135 +++++- src/swagger-output.json | 426 +++++++++++++----- 5 files changed, 683 insertions(+), 131 deletions(-) diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index 323e207..8e51fdf 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -50,10 +50,11 @@ export class MedicalRecordController { }); - public getRecordFile = catchAsync(async (req: Request, res: Response): Promise => { + public getRecordFile = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const recordId = req.params.id; + const clinicId = req.params.clinicId; - const { buffer, ...metadata } = await this.medicalRecordService.getRecordFile(recordId); + const { buffer, ...metadata } = await this.medicalRecordService.getRecordFile(clinicId, recordId); res.status(200).json({ message: 'Medical record retrieved successfully', @@ -69,16 +70,70 @@ export class MedicalRecordController { res.status(200).json(result); }); - public deleteRecord = catchAsync(async (req: Request, res: Response): Promise => { + public deleteRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const recordId = req.params.id; + const clinicId = req.params.clinicId; - await this.medicalRecordService.deleteRecord(recordId); + await this.medicalRecordService.deleteRecord(clinicId, recordId); res.status(200).json({ message: 'Medical record deleted successfully', }); }); + public grantAccess = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const { targetClinicId } = req.body; + + await this.medicalRecordService.grantAccess(patientId, targetClinicId); + + res.status(200).json({ + message: 'Access granted successfully', + }); + }); + + public addDoctorRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + if (!req.file) { + res.status(400).json({ message: 'No file uploaded' }); + return; + } + + const clinicId = req.params.clinicId; + const patientId = req.params.patientId; + const doctorId = req.user.id; + const recordData: CreateMedicalRecordDto = req.body; + const fileBuffer = req.file.buffer; + const fileName = req.file.originalname; + const mimeType = req.file.mimetype; + + const recordId = await this.medicalRecordService.addDoctorRecord( + clinicId, + patientId, + doctorId, + recordData, + fileBuffer, + fileName, + mimeType, + ); + + res.status(201).json({ + message: 'Medical record uploaded successfully', + data: { recordId }, + }); + }); + + public getSOAPNotes = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const clinicId = req.params.clinicId; + const patientId = req.params.patientId; + + const notes = await this.medicalRecordService.getSOAPNotes(clinicId, patientId); + + res.status(200).json({ + message: 'SOAP notes retrieved successfully', + data: notes, + }); + }); + // // metadata only // public getRecordMetadata = catchAsync(async (req: Request, res: Response): Promise => { // const record = await this.medicalRecordService.getRecordMetadata(req.params.id); diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index c77a721..d6d602b 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -160,8 +160,6 @@ export class FabricRoute implements Routes { required: true, schema: { $targetMsp: 'Org2MSP' } } - */ - } */ this.fabricController.grantAccess, ); diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 4921eaf..49173ae 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -80,12 +80,12 @@ export class MedicalRecordRoute implements Routes { ); this.router.get( - `${this.path}/:id`, + `${this.path}/:clinicId/:id`, /* - #swagger.path = '/record/{id}' + #swagger.path = '/record/{clinicId}/{id}' #swagger.method = 'get' #swagger.tags = ['Medical Records'] - #swagger.description = 'Downloads and decrypts a single medical record file. Returns raw file bytes with appropriate Content-Type header.' + #swagger.description = 'Downloads and decrypts a single medical record file. The clinicId identifies the caller\'s clinic for on-chain authorization.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -94,6 +94,13 @@ export class MedicalRecordRoute implements Routes { type: 'string' } + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'UUID of the caller\'s clinic (used for on-chain access check)', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { in: 'path', description: 'UUID of the medical record', @@ -102,11 +109,14 @@ export class MedicalRecordRoute implements Routes { } #swagger.responses[200] = { - description: 'Decrypted file bytes streamed back with Content-Type, Content-Disposition, x-record-id, x-patient-id, x-record-type headers set' + description: 'Decrypted file returned as base64 with record metadata' } #swagger.responses[401] = { description: 'Unauthorized – missing or invalid token' } + #swagger.responses[403] = { + description: 'Access denied – clinic not authorized for this record' + } #swagger.responses[404] = { description: 'Record not found or already deleted' } @@ -198,12 +208,12 @@ export class MedicalRecordRoute implements Routes { this.router.delete( - `${this.path}/:id`, + `${this.path}/:clinicId/:id`, /* - #swagger.path = '/record/{id}' + #swagger.path = '/record/{clinicId}/{id}' #swagger.method = 'delete' #swagger.tags = ['Medical Records'] - #swagger.description = 'Soft-deletes a medical record (sets deleted_at). File remains on IPFS but is inaccessible via the API.' + #swagger.description = 'Soft-deletes a medical record. The clinicId identifies the caller\'s clinic; chaincode enforces owner-only deletion.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -212,6 +222,13 @@ export class MedicalRecordRoute implements Routes { type: 'string' } + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'UUID of the caller\'s clinic (must be the record owner)', + required: true, + type: 'string' + } + #swagger.parameters['id'] = { in: 'path', description: 'UUID of the medical record to delete', @@ -221,13 +238,14 @@ export class MedicalRecordRoute implements Routes { #swagger.responses[200] = { description: 'Medical record deleted successfully', - schema: { - message: 'Medical record deleted successfully' - } + schema: { message: 'Medical record deleted successfully' } } #swagger.responses[401] = { description: 'Unauthorized – missing or invalid token' } + #swagger.responses[403] = { + description: 'Only the owner clinic can delete this record' + } #swagger.responses[404] = { description: 'Record not found or already deleted' } @@ -236,5 +254,155 @@ export class MedicalRecordRoute implements Routes { RoleMiddleware(Role.PATIENT, Role.DOCTOR), this.medicalRecordController.deleteRecord ); + + this.router.post( + `${this.path}/grant-access`, + /* + #swagger.path = '/record/grant-access' + #swagger.method = 'post' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Patient grants a target clinic access to ALL their medical records across all owner clinics.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { targetClinicId: 'uuid-string' } + } + + #swagger.responses[200] = { + description: 'Access granted successfully', + schema: { message: 'Access granted successfully' } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.grantAccess + ); + + this.router.post( + `${this.path}/:clinicId/:patientId/doctor-upload`, + /* + #swagger.path = '/record/{clinicId}/{patientId}/doctor-upload' + #swagger.method = 'post' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Doctor uploads a medical record for a patient. Validates the doctor works at the clinic. The clinic identity is used for blockchain operations.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'UUID of the clinic', + required: true, + type: 'string' + } + + #swagger.parameters['patientId'] = { + in: 'path', + description: 'UUID of the patient', + required: true, + type: 'string' + } + + #swagger.parameters['file'] = { + in: 'formData', + description: 'The medical record file', + required: true, + type: 'file' + } + + #swagger.parameters['name'] = { + in: 'formData', + description: 'Display name for the record', + required: true, + type: 'string' + } + + #swagger.parameters['type'] = { + in: 'formData', + description: 'Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)', + required: true, + type: 'string' + } + + #swagger.responses[201] = { + description: 'Medical record uploaded successfully', + schema: { message: 'Medical record uploaded successfully', data: { recordId: 'uuid-string' } } + } + #swagger.responses[400] = { + description: 'No file uploaded or validation failed' + } + #swagger.responses[403] = { + description: 'Doctor is not associated with this clinic' + } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + uploadSingleFile, + ValidationMiddleware(CreateMedicalRecordDto), + this.medicalRecordController.addDoctorRecord + ); + + this.router.get( + `${this.path}/:clinicId/:patientId/soap-notes`, + /* + #swagger.path = '/record/{clinicId}/{patientId}/soap-notes' + #swagger.method = 'get' + #swagger.tags = ['Medical Records'] + #swagger.description = 'Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, and returns their parsed contents. Requires on-chain authorization.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['clinicId'] = { + in: 'path', + description: 'UUID of the caller\'s clinic (for on-chain access check)', + required: true, + type: 'string' + } + + #swagger.parameters['patientId'] = { + in: 'path', + description: 'UUID of the patient', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'SOAP notes retrieved successfully', + schema: { + message: 'SOAP notes retrieved successfully', + data: [{ recordId: 'uuid-string', content: {} }] + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + #swagger.responses[403] = { + description: 'Access denied – clinic not authorized' + } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.medicalRecordController.getSOAPNotes + ); } } \ No newline at end of file diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 8f5183b..3bdc90c 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -60,8 +60,7 @@ export class MedicalRecordService { ipfsCidKey: cid, }); } - - public async getRecordFile(recordId: string): Promise { + public async getRecordFile(callerClinicId: string, recordId: string): Promise { const record = await prisma.medicalRecord.findFirst({ where: { id: recordId, @@ -85,8 +84,18 @@ export class MedicalRecordService { throw new HttpException(error.status, error.message, error.messageAr); } + // Verify the caller's clinic is authorized on-chain. + // GetRecordsByPatient enforces MSP authorization — if the caller is not the owner + // or not in authorizedMsps, the record won't appear in the result. + const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, record.patient_id); + const isAuthorized = authorizedRecords.some(r => r.recordId === recordId); + if (!isAuthorized) { + throw new HttpException(403, 'Access denied: your clinic is not authorized to access this record'); + } + const encryptedFile = await this.ipfsService.getFile(record.cid); + // Decrypt using the owner clinic's key (the clinic that created the record) const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); recordDEK.fill(0); @@ -108,7 +117,6 @@ export class MedicalRecordService { public async checkIpfsHealth(): Promise<{ status: string; message: string }> { return this.ipfsService.checkHealth(); } - public async getPatientFiles(patientId: string): Promise { const records = await prisma.medicalRecord.findMany({ where: { @@ -131,6 +139,8 @@ export class MedicalRecordService { }, }); + + return records.map(record => ({ id: record.id, patient_id: record.patient_id, @@ -144,8 +154,7 @@ export class MedicalRecordService { })); } - // delete any MR (soft) - public async deleteRecord(recordId: string): Promise { + public async deleteRecord(callerClinicId: string, recordId: string): Promise { const record = await prisma.medicalRecord.findFirst({ where: { id: recordId, @@ -163,8 +172,8 @@ export class MedicalRecordService { throw new HttpException(error.status, error.message, error.messageAr); } - // Remove from blockchain ledger - await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, recordId); + // Chaincode enforces ownerMSP check — non-owners get a chaincode error + await this.fabricService.deleteRecord(callerClinicId, record.patient_id, recordId); // Soft-delete in DB await prisma.medicalRecord.update({ @@ -172,4 +181,116 @@ export class MedicalRecordService { data: { deleted_at: new Date() }, }); } + + + public async grantAccess(patientId: string, targetClinicId: string): Promise { + // Fetch all records for this patient to find distinct owner clinics + const records = await prisma.medicalRecord.findMany({ + where: { patient_id: patientId, deleted_at: null }, + select: { clinic_id: true }, + }); + + // Group by owner clinic — each clinic MSP must grant independently + const ownerClinicIds = [...new Set(records.map(r => r.clinic_id))]; + + for (const ownerClinicId of ownerClinicIds) { + await this.fabricService.grantAccess(ownerClinicId, patientId, targetClinicId); + } + } + + /** + * Doctor-initiated record creation. + * Validates the doctor works in the clinic, encrypts the file, uploads to IPFS, + * stores in DB, syncs to blockchain, and returns the record ID. + */ + public async addDoctorRecord( + clinicId: string, + patientId: string, + doctorId: string, + fileData: CreateMedicalRecordDto, + fileBuffer: Buffer, + fileName: string, + mimeType: string, + ): Promise { + // Validate the doctor is associated with this clinic + const clinicDoctor = await prisma.clinicDoctor.findUnique({ + where: { clinic_id_doctor_id: { clinic_id: clinicId, doctor_id: doctorId } }, + }); + if (!clinicDoctor) { + throw new HttpException(403, 'Doctor is not associated with this clinic'); + } + + const recordId = randomUUID(); + + const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(fileBuffer, recordDEK); + recordDEK.fill(0); + + const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + + await prisma.medicalRecord.create({ + data: { + id: recordId, + patient_id: patientId, + doctor_id: doctorId, + clinic_id: clinicId, + name: fileData.name, + cid: cid, + type: fileData.type, + mime_type: mimeType, + } as Prisma.MedicalRecordUncheckedCreateInput, + }); + + // Sync to blockchain ledger + await this.fabricService.addRecord(clinicId, { + patientId, + recordId, + doctorId, + type: fileData.type, + ipfsCidKey: cid, + }); + + return recordId; + } + + /** + * Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, + * and returns their parsed contents as a list of objects. + */ + public async getSOAPNotes(callerClinicId: string, patientId: string): Promise> { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + type: 'SOAP_NOTE', + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + // Verify on-chain access once for this patient + const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); + const authorizedIds = new Set(authorizedRecords.map(r => r.recordId)); + + const results: Array<{ recordId: string; content: any }> = []; + + for (const record of records) { + if (!authorizedIds.has(record.id)) continue; + + const encryptedFile = await this.ipfsService.getFile(record.cid); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); + + const content = JSON.parse(decryptedFile.toString('utf-8')); + results.push({ recordId: record.id, content }); + } + + return results; + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 624b265..6b9ef65 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -923,9 +923,9 @@ "schema": { "type": "object", "properties": { - "label": { + "clinicId": { "type": "string", - "example": "org1" + "example": "clinic-uuid-here" }, "mspId": { "type": "string", @@ -961,7 +961,7 @@ } }, "required": [ - "label", + "clinicId", "mspId", "certificate", "privateKey", @@ -992,7 +992,7 @@ } } }, - "/fabric/identities/{label}": { + "/fabric/identities/{clinicId}": { "delete": { "tags": [ "FabricIdentity" @@ -1000,7 +1000,7 @@ "description": "", "parameters": [ { - "name": "label", + "name": "clinicId", "in": "path", "required": true, "type": "string" @@ -1031,14 +1031,51 @@ "tags": [ "FabricIdentity" ], - "description": "", + "description": "Initialize the ledger, optionally seeding it with backup records", "parameters": [ { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", - "required": true, - "type": "string" + "name": "body", + "in": "body", + "description": "Optional backup data to seed the ledger", + "required": false, + "schema": { + "type": "object", + "properties": { + "backupData": { + "type": "array", + "items": { + "type": "object", + "properties": { + "patientId": { + "type": "string", + "example": "patient-uuid" + }, + "recordId": { + "type": "string", + "example": "record-uuid" + }, + "doctorId": { + "type": "string", + "example": "doctor-uuid" + }, + "type": { + "type": "string", + "example": "LAB_RESULT" + }, + "ownerMsp": { + "type": "string", + "example": "Org1MSP" + }, + "authorizedMsps": { + "type": "array", + "example": [], + "items": {} + } + } + } + } + } + } } ], "responses": { @@ -1073,64 +1110,42 @@ "tags": [ "MedicalRecords" ], - "description": "", + "description": "Add a new medical record for a patient", "parameters": [ - { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", - "required": true, - "type": "string" - }, { "name": "body", "in": "body", - "description": "Medical record data", "required": true, "schema": { "type": "object", "properties": { "patientId": { "type": "string", - "example": "P12345" - }, - "firstName": { - "type": "string", - "example": "John" + "example": "patient-uuid" }, - "lastName": { + "recordId": { "type": "string", - "example": "Doe" + "example": "record-uuid" }, - "dateOfBirth": { - "type": "string", - "example": "1990-01-01" - }, - "gender": { - "type": "string", - "example": "Male" - }, - "bloodType": { + "doctorId": { "type": "string", - "example": "O+" + "example": "doctor-uuid" }, - "ipfsCid": { + "type": { "type": "string", - "example": "Qm..." + "example": "LAB_RESULT" }, - "summary": { + "ipfsCidKey": { "type": "string", - "example": "Optional summary" + "example": "bafybeigdyrzt..." } }, "required": [ "patientId", - "firstName", - "lastName", - "dateOfBirth", - "gender", - "bloodType", - "ipfsCid" + "recordId", + "doctorId", + "type", + "ipfsCidKey" ] } } @@ -1160,20 +1175,13 @@ "tags": [ "MedicalRecords" ], - "description": "", + "description": "Get all records for a patient (authorized MSPs only)", "parameters": [ { "name": "patientId", "in": "path", "required": true, "type": "string" - }, - { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", - "required": true, - "type": "string" } ], "responses": { @@ -1181,12 +1189,14 @@ "description": "" } } - }, + } + }, + "/records/{patientId}/{recordId}": { "put": { "tags": [ "MedicalRecords" ], - "description": "", + "description": "Update an existing medical record (doctorId, type, optional new ipfsCidKey via transient)", "parameters": [ { "name": "patientId", @@ -1195,56 +1205,39 @@ "type": "string" }, { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", + "name": "recordId", + "in": "path", "required": true, "type": "string" }, { "name": "body", "in": "body", - "description": "Update medical record data", "required": true, "schema": { "type": "object", "properties": { - "firstName": { + "recordId": { "type": "string", - "example": "John" + "example": "uuid-record-id" }, - "lastName": { - "type": "string", - "example": "Doe" - }, - "dateOfBirth": { - "type": "string", - "example": "1990-01-01" - }, - "gender": { - "type": "string", - "example": "Male" - }, - "bloodType": { + "doctorId": { "type": "string", - "example": "O+" + "example": "doctor-uuid" }, - "ipfsCid": { + "type": { "type": "string", - "example": "Qm..." + "example": "LAB_RESULT" }, - "summary": { + "ipfsCidKey": { "type": "string", - "example": "Optional summary" + "example": "optional-new-cid-key" } }, "required": [ - "firstName", - "lastName", - "dateOfBirth", - "gender", - "bloodType", - "ipfsCid" + "recordId", + "doctorId", + "type" ] } } @@ -1261,7 +1254,7 @@ "tags": [ "MedicalRecords" ], - "description": "", + "description": "Grant access to all records of a patient for a target MSP", "parameters": [ { "name": "patientId", @@ -1269,17 +1262,9 @@ "required": true, "type": "string" }, - { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", - "required": true, - "type": "string" - }, { "name": "body", "in": "body", - "description": "Grant access to MSP", "required": true, "schema": { "type": "object", @@ -8666,13 +8651,20 @@ } } }, - "/record/{id}": { + "/record/{clinicId}/{id}": { "get": { "tags": [ "Medical Records" ], - "description": "Downloads and decrypts a single medical record file. Returns raw file bytes with appropriate Content-Type header.", + "description": "Downloads and decrypts a single medical record file. The clinicId identifies the caller\\'s clinic for on-chain authorization.", "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the caller's clinic (used for on-chain access check)" + }, { "name": "id", "in": "path", @@ -8690,11 +8682,14 @@ ], "responses": { "200": { - "description": "Decrypted file bytes streamed back with Content-Type, Content-Disposition, x-record-id, x-patient-id, x-record-type headers set" + "description": "Decrypted file returned as base64 with record metadata" }, "401": { "description": "Unauthorized – missing or invalid token" }, + "403": { + "description": "Access denied – clinic not authorized for this record" + }, "404": { "description": "Record not found or already deleted" } @@ -8704,8 +8699,15 @@ "tags": [ "Medical Records" ], - "description": "Soft-deletes a medical record (sets deleted_at). File remains on IPFS but is inaccessible via the API.", + "description": "Soft-deletes a medical record. The clinicId identifies the caller\\'s clinic; chaincode enforces owner-only deletion.", "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the caller's clinic (must be the record owner)" + }, { "name": "id", "in": "path", @@ -8740,19 +8742,29 @@ "401": { "description": "Unauthorized – missing or invalid token" }, + "403": { + "description": "Only the owner clinic can delete this record" + }, "404": { "description": "Record not found or already deleted" } } } }, - "/record/{doctorId}/upload": { + "/record/{clinicId}/{doctorId}/upload": { "post": { "tags": [ "Medical Records" ], - "description": "Patient uploads a new medical record file", + "description": "Patient uploads a new medical record file. The clinic identity is used to store the encryption key on the blockchain.", "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the clinic whose Fabric identity will store the encryption key" + }, { "name": "doctorId", "in": "path", @@ -8788,18 +8800,140 @@ "required": true, "type": "string" }, + { + "name": "appointmentId", + "in": "formData", + "description": "UUID of the appointment (optional)", + "required": false, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Medical record uploaded successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical record uploaded successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "No file uploaded or validation failed" + }, + "401": { + "description": "Unauthorized – missing or invalid token" + }, + "404": { + "description": "Patient encryption key not found" + } + } + } + }, + "/record/grant-access": { + "post": { + "tags": [ + "Medical Records" + ], + "description": "Patient grants a target clinic access to ALL their medical records across all owner clinics.", + "parameters": [ + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "targetClinicId": { + "type": "string", + "example": "uuid-string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Access granted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Access granted successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "401": { + "description": "Unauthorized – missing or invalid token" + } + } + } + }, + "/record/{clinicId}/{patientId}/doctor-upload": { + "post": { + "tags": [ + "Medical Records" + ], + "description": "Doctor uploads a medical record for a patient. Validates the doctor works at the clinic. The clinic identity is used for blockchain operations.", + "parameters": [ { "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the clinic" + }, + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the patient" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "file", "in": "formData", - "description": "UUID of the clinic", + "description": "The medical record file", + "required": true, + "type": "file" + }, + { + "name": "name", + "in": "formData", + "description": "Display name for the record", "required": true, "type": "string" }, { - "name": "appointmentId", + "name": "type", "in": "formData", - "description": "UUID of the appointment (optional)", - "required": false, + "description": "Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)", + "required": true, "type": "string" } ], @@ -8812,6 +8946,15 @@ "message": { "type": "string", "example": "Medical record uploaded successfully" + }, + "data": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + } + } } }, "xml": { @@ -8822,11 +8965,78 @@ "400": { "description": "No file uploaded or validation failed" }, + "403": { + "description": "Doctor is not associated with this clinic" + } + } + } + }, + "/record/{clinicId}/{patientId}/soap-notes": { + "get": { + "tags": [ + "Medical Records" + ], + "description": "Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, and returns their parsed contents. Requires on-chain authorization.", + "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the caller's clinic (for on-chain access check)" + }, + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the patient" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "SOAP notes retrieved successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "SOAP notes retrieved successfully" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + }, + "content": { + "type": "object", + "properties": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, "401": { "description": "Unauthorized – missing or invalid token" }, - "404": { - "description": "Patient encryption key not found" + "403": { + "description": "Access denied – clinic not authorized" } } } From e7435ff494c5b3ca5af32db8e5c8d5b1ef195424 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 8 Mar 2026 00:55:55 +0200 Subject: [PATCH 190/210] update appointments / schedule --- src/services/appointment.service.ts | 33 +++++------------------------ src/services/queue.service.ts | 10 ++++----- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 8576ad3..a2f78ba 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -268,6 +268,7 @@ export class AppointmentService { doctor_id: doctorId, clinic_id: isOnline ? null : clinicId, scheduled_time: scheduledTime, + status: 'CONFIRMED', slot_duration: schedule.slot_duration, end_time: endTime, is_online: isOnline, @@ -427,24 +428,7 @@ export class AppointmentService { return []; } for (const appointment of appointments) { - if (appointment.status === 'CONFIRMED') { - await this.queueService.calculateQueuePosition(appointment.id); - - const refreshed = await prisma.appointment.findUnique({ - where: { id: appointment.id }, - select: { - position: true, - estimated_time: true, - patients_ahead: true, - } - }); - - if (refreshed) { - appointment.position = refreshed.position; - appointment.estimated_time = refreshed.estimated_time; - appointment.patients_ahead = refreshed.patients_ahead; - } - } + const queueParameters = await this.queueService.getQueuePosition(appointment.id); result.push({ id: appointment.id, @@ -461,15 +445,13 @@ export class AppointmentService { clinic_name: appointment.clinic ? appointment.clinic.name : null, clinic_address: appointment.clinic ? appointment.clinic.address : null, address_maps_link: appointment.clinic ? appointment.clinic.address_maps_link : null, - position: appointment.position, - estimatedWaitMinutes: appointment.estimated_time, - patientsAhead: appointment.patients_ahead + position: queueParameters.position, + estimatedWaitMinutes: queueParameters.estimatedWaitMinutes, + patientsAhead: queueParameters.patientsAhead }); } return result; - - } public async rescheduleAppointmentByPatient(patientId: string, appointmentId: string, newScheduledTime: Date): Promise { @@ -850,9 +832,6 @@ export class AppointmentService { gte: startOfDay, lte: endOfDay }, - status: { - in: ['CONFIRMED', 'COMPLETED'] - }, deleted_at: null, }, select: { @@ -988,8 +967,6 @@ export class AppointmentService { const egyptOffset = 2 * 60 * 60 * 1000; const now = new Date(nowUTC.getTime() + egyptOffset); - console.log('Current time in Egypt:', now); - console.log('Appointment scheduled time:', appointment.scheduled_time); if (now < appointment.scheduled_time) { const error = createBilingualError(400, ErrorMessages.CANNOT_BE_COMPLETED_BEFORE_SCHEDULED_TIME); diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 97b817c..0764b10 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -67,10 +67,6 @@ export class QueueService { } }); - if (!schedule){ - const error = createBilingualError(404, ErrorMessages.DOCTOR_NOT_WORKING_ON_DAY); - throw new HttpException(error.status, error.message, error.messageAr); - } const bufferTime = schedule?.buffer_time || 0; const startOfDay = new Date(appointment.scheduled_time); @@ -114,10 +110,12 @@ export class QueueService { const nowUTC = new Date(); const egyptOffset = 2 * 60 * 60 * 1000; const now = new Date(nowUTC.getTime() + egyptOffset); + let estimatedWaitMinutes = 0 // const estimatedWaitMinutes = appointmentsAhead.reduce((total, app) => total + app.slot_duration + bufferTime, 0); - const estimatedWaitMinutes = Math.max(0, Math.round((appointment.scheduled_time.getTime() - now.getTime()) / (1000 * 60))); - + if (appointment.scheduled_time.getTime() >= now.getTime()){ + estimatedWaitMinutes = Math.max(0, Math.round((appointment.scheduled_time.getTime() - now.getTime()) / (1000 * 60))); + } this.updateQueueParameters(appointmentId, position, patientsAhead, estimatedWaitMinutes); } From 96effaa5b33d69b908ebc6cb3af8feabc02256a5 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 8 Mar 2026 16:57:34 +0200 Subject: [PATCH 191/210] update get working nurses by doctor --- src/interfaces/nurse.interface.ts | 87 +++++++++++++++++++------------ src/routes/doctors.route.ts | 40 ++++++++++++-- src/services/doctor.service.ts | 80 ++++++++++++++++++++-------- src/swagger-output.json | 54 +++++++++++++++++-- 4 files changed, 198 insertions(+), 63 deletions(-) diff --git a/src/interfaces/nurse.interface.ts b/src/interfaces/nurse.interface.ts index e0bd040..84900b2 100644 --- a/src/interfaces/nurse.interface.ts +++ b/src/interfaces/nurse.interface.ts @@ -1,30 +1,30 @@ -import { NurseAccountStatus, Gender, AnnouncementStatus, AnnouncementNurseStatus} from "@prisma/client"; +import { NurseAccountStatus, Gender, AnnouncementStatus, AnnouncementNurseStatus } from "@prisma/client"; import { WorkingDays } from "./doctors.interface"; export interface NurseLoginData { - id: string, - name: string, - email: string, - username: string, - phone: string, - gender: string, - nurse: { - account_status: NurseAccountStatus, - } + id: string, + name: string, + email: string, + username: string, + phone: string, + gender: string, + nurse: { + account_status: NurseAccountStatus, + } } export interface NurseData { - id: string; - name: string; - email: string; - phone: string; - gender: Gender; - age: number; - profilePic: string | null; - years_of_experience: number; - nationalCardUrl: string; - brief: string | null; - bonusFileUrl: string | null; + id: string; + name: string; + email: string; + phone: string; + gender: Gender; + age: number; + profilePic: string | null; + years_of_experience: number; + nationalCardUrl: string; + brief: string | null; + bonusFileUrl: string | null; } export interface NurseApplications { @@ -51,18 +51,39 @@ export interface NurseApplications { } export interface NurseSchedule { + id: string; + doctor: { + id: string; + name: string; + gender: Gender; + profilePic: string; + }; + clinic: { id: string; - doctor: { - id: string; - name: string; - gender: Gender; - profilePic: string; - }; - clinic: { - id: string; - name: string - address: string; - address_maps_link: string; - }; + name: string + address: string; + address_maps_link: string; + }; + working_days: WorkingDays[]; +} + +export interface NurseFullDetails { + id: string; + name: string; + email: string; + phone: string; + gender: Gender; + age: number; + profilePic: string | null; + years_of_experience: number; + nationalCardUrl: string; + brief: string | null; + bonusFileUrl: string | null; + clinics: { + id: string; + name: string; + address: string; + address_maps_link: string; working_days: WorkingDays[]; + }[]; } \ No newline at end of file diff --git a/src/routes/doctors.route.ts b/src/routes/doctors.route.ts index 7dd7acb..23052a9 100644 --- a/src/routes/doctors.route.ts +++ b/src/routes/doctors.route.ts @@ -385,12 +385,45 @@ export class DoctorsRoute implements Routes { email: 'max.mustermann@example.com', gender: 'FEMALE', phone: '+201234567890', - age: 28, + age: 25, profilePic: 'https://res.cloudinary.com/example/photo.jpg', - years_of_experience: 5, + years_of_experience: 2, nationalCardUrl: 'https://res.cloudinary.com/example/national_card.pdf', + bonusFileUrl: 'https://res.cloudinary.com/example/bonus.pdf', brief: 'Experienced ICU nurse with 5 years in critical care', - bonusFileUrl: 'https://res.cloudinary.com/example/bonus.pdf' + clinics: [ + { + id: 'uuid-string', + name: 'Al Salam Clinic', + address: '123 Main St, Cairo', + address_maps_link: 'https://maps.google.com/?q=...', + working_days: [ + { + day_of_week: 'SUNDAY', + start_time: '14:00', + end_time: '17:00' + } + ] + }, + { + id: 'uuid-string', + name: 'Medical Park Clinic', + address: '123 Main St, New Cairo', + address_maps_link: 'https://maps.google.com/?q=...', + working_days: [ + { + day_of_week: 'MONDAY', + start_time: '10:00', + end_time: '17:00' + }, + { + day_of_week: 'TUESDAY', + start_time: '10:00', + end_time: '17:00' + } + ] + } + ] } ], messageEn: 'Nurses retrieved successfully', @@ -401,6 +434,7 @@ export class DoctorsRoute implements Routes { description: 'Unauthorized – missing or invalid token' } */ + AuthMiddleware, this.doctorsController.getWorkingNurses ) diff --git a/src/services/doctor.service.ts b/src/services/doctor.service.ts index d7b2e10..bd2d2b0 100644 --- a/src/services/doctor.service.ts +++ b/src/services/doctor.service.ts @@ -5,7 +5,7 @@ import { ErrorMessages, createBilingualError } from "@/utils/errorMessages"; import { Doctor, DoctorAccountStatus, PrismaClient, Role } from "@prisma/client"; import { hash, compare } from "bcrypt"; import { DoctorLoginData, DoctorPersonalData, DoctorAnnouncements } from "@/interfaces/doctors.interface"; -import { NurseData } from "@/interfaces/nurse.interface"; +import { NurseData, NurseFullDetails } from "@/interfaces/nurse.interface"; import { AuthService } from "./auth.service"; import prisma from "@/config/prisma"; import cloudinary from "@/utils/cloudinary"; @@ -705,8 +705,8 @@ export class DoctorService { } await prisma.announcement.update({ - where: { - id: announcementId + where: { + id: announcementId }, data: updateData }); @@ -775,15 +775,19 @@ export class DoctorService { }))); } - public async getWorkingNurses(doctorId: string): Promise { - const nurses = await prisma.nurseSchedule.findMany({ + public async getWorkingNurses(doctorId: string): Promise { + const workingNurses = await prisma.nurseSchedule.findMany({ where: { doctor_id: doctorId, deleted_at: null }, + orderBy: { + day_of_week: 'asc', + }, select: { nurse: { select: { + id: true, years_of_experience: true, nationalCardUrl: true, bonusFileUrl: true, @@ -800,27 +804,59 @@ export class DoctorService { } } } - } + }, + clinic: { + select: { + id: true, + name: true, + address: true, + address_maps_link: true, + } + }, + day_of_week: true, + start_time: true, + end_time: true, } }); + const result: NurseFullDetails[] = []; - const uniqueNurses = Array.from( - new Map(nurses.map(({ nurse }) => [nurse.user.id, nurse])).values() - ); + for (const row of workingNurses) { + const workingDay = { + day_of_week: row.day_of_week, + start_time: row.start_time, + end_time: row.end_time + }; - return Promise.all(uniqueNurses.map(async (nurse) => ({ - id: nurse.user.id, - name: nurse.user.name, - email: nurse.user.email, - gender: nurse.user.gender, - phone: nurse.user.phone, - age: await this.userService.calculateUserAge(nurse.user.date_of_birth), - profilePic: nurse.user.photo_url, - years_of_experience: nurse.years_of_experience, - nationalCardUrl: nurse.nationalCardUrl, - bonusFileUrl: nurse.bonusFileUrl, - brief: nurse.brief, - }))); + let nurse = result.find(n => n.id === row.nurse.id); + + if (!nurse) { + nurse = { + id: row.nurse.id, + name: row.nurse.user.name, + email: row.nurse.user.email, + gender: row.nurse.user.gender, + phone: row.nurse.user.phone, + age: await this.userService.calculateUserAge(row.nurse.user.date_of_birth), + profilePic: row.nurse.user.photo_url, + years_of_experience: row.nurse.years_of_experience, + nationalCardUrl: row.nurse.nationalCardUrl, + bonusFileUrl: row.nurse.bonusFileUrl, + brief: row.nurse.brief, + clinics: [], + }; + result.push(nurse); + } + + let clinic = nurse.clinics.find(c => c.id === row.clinic?.id); + + if (!clinic) { + clinic = { ...row.clinic, working_days: [] }; + nurse.clinics.push(clinic); + } + + clinic.working_days.push(workingDay); + } + return result; } public async postAnnouncement(doctorId: string, data: PostAnnouncementDto): Promise { diff --git a/src/swagger-output.json b/src/swagger-output.json index 38b7c87..82e2980 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -4635,7 +4635,7 @@ }, "age": { "type": "number", - "example": 28 + "example": 25 }, "profilePic": { "type": "string", @@ -4643,19 +4643,63 @@ }, "years_of_experience": { "type": "number", - "example": 5 + "example": 2 }, "nationalCardUrl": { "type": "string", "example": "https://res.cloudinary.com/example/national_card.pdf" }, + "bonusFileUrl": { + "type": "string", + "example": "https://res.cloudinary.com/example/bonus.pdf" + }, "brief": { "type": "string", "example": "Experienced ICU nurse with 5 years in critical care" }, - "bonusFileUrl": { - "type": "string", - "example": "https://res.cloudinary.com/example/bonus.pdf" + "clinics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "uuid-string" + }, + "name": { + "type": "string", + "example": "Medical Park Clinic" + }, + "address": { + "type": "string", + "example": "123 Main St, New Cairo" + }, + "address_maps_link": { + "type": "string", + "example": "https://maps.google.com/?q=..." + }, + "working_days": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "string", + "example": "TUESDAY" + }, + "start_time": { + "type": "string", + "example": "10:00" + }, + "end_time": { + "type": "string", + "example": "17:00" + } + } + } + } + } + } } } } From db349a757be8e7a890f6689f159aa05065cd1aec Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sun, 8 Mar 2026 22:22:43 +0200 Subject: [PATCH 192/210] feat: medical records basic endpoints --- src/controllers/medical-records.controller.ts | 140 ++----- src/dtos/medical-records.dto.ts | 14 +- src/interfaces/enums.interface.ts | 3 +- src/middlewares/auth.middleware.ts | 4 +- src/routes/medical-record.route.ts | 381 +++++------------- src/server.ts | 13 + src/services/encryption.service.ts | 1 + src/services/fabric.service.ts | 64 +-- src/services/medical-records.service.ts | 166 +++++++- src/swagger-output.json | 341 ++++++---------- src/swagger.mjs | 27 +- 11 files changed, 501 insertions(+), 653 deletions(-) diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index 8e51fdf..d82fafd 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -1,4 +1,4 @@ -import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { Request, Response } from 'express'; import { RequestWithUser } from '@/interfaces/auth.interface'; import { MedicalRecordService } from '@/services/medical-records.service'; @@ -9,36 +9,13 @@ export class MedicalRecordController { private medicalRecordService = new MedicalRecordService(); - public uploadRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - if (!req.file) { - res.status(400).json({ message: 'No file uploaded' }); - return; - } - const recordData: CreateMedicalRecordDto = req.body; - const patientId = req.user.id; - const doctorId = req.params.doctorId; - const clinicId = req.params.clinicId; - const fileBuffer = req.file.buffer; - const fileName = req.file.originalname; - const mimeType = req.file.mimetype; - - await this.medicalRecordService.createMedicalRecord( - clinicId, - patientId, - doctorId, - recordData, - fileBuffer, - fileName, - mimeType, - ); - - res.status(201).json({ - message: 'Medical record uploaded successfully', - }); + public checkIpfsHealth = catchAsync(async (req: Request, res: Response): Promise => { + const result = await this.medicalRecordService.checkIpfsHealth(); + res.status(200).json(result); }); - public getPatientMedicalRecords = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getRecordsMetadata = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; const records = await this.medicalRecordService.getPatientFiles(patientId); @@ -49,35 +26,34 @@ export class MedicalRecordController { }); }); - - public getRecordFile = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - const recordId = req.params.id; + // this function adds json based data only + public addRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const clinicId = req.params.clinicId; + const patientId = req.params.patientId; + const doctorId = req.user.id; + const dto: CreateDoctorRecordJsonDto = req.body; - const { buffer, ...metadata } = await this.medicalRecordService.getRecordFile(clinicId, recordId); + const recordId = await this.medicalRecordService.addDoctorRecord( + clinicId, + patientId, + doctorId, + dto, + ); - res.status(200).json({ - message: 'Medical record retrieved successfully', - data: { - ...metadata, - file: buffer.toString('base64'), - }, + res.status(201).json({ + message: 'Medical record created successfully', + data: { recordId }, }); }); - public checkIpfsHealth = catchAsync(async (req: Request, res: Response): Promise => { - const result = await this.medicalRecordService.checkIpfsHealth(); - res.status(200).json(result); - }); - - public deleteRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - const recordId = req.params.id; - const clinicId = req.params.clinicId; + public getSOAPNotes = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; - await this.medicalRecordService.deleteRecord(clinicId, recordId); + const notes = await this.medicalRecordService.getSOAPNotesForPatient(patientId); res.status(200).json({ - message: 'Medical record deleted successfully', + message: 'SOAP notes retrieved successfully', + data: notes, }); }); @@ -91,63 +67,23 @@ export class MedicalRecordController { message: 'Access granted successfully', }); }); - - public addDoctorRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - if (!req.file) { - res.status(400).json({ message: 'No file uploaded' }); - return; - } - - const clinicId = req.params.clinicId; - const patientId = req.params.patientId; - const doctorId = req.user.id; - const recordData: CreateMedicalRecordDto = req.body; - const fileBuffer = req.file.buffer; - const fileName = req.file.originalname; - const mimeType = req.file.mimetype; - - const recordId = await this.medicalRecordService.addDoctorRecord( - clinicId, - patientId, - doctorId, - recordData, - fileBuffer, - fileName, - mimeType, - ); - - res.status(201).json({ - message: 'Medical record uploaded successfully', - data: { recordId }, + // DEV ONLY — no auth + public deleteAllRecords = catchAsync(async (_req: Request, res: Response): Promise => { + const result = await this.medicalRecordService.deleteAllRecords(); + res.status(200).json({ + message: `Deleted ${result.deleted} records from DB, IPFS, and blockchain`, + data: result, }); }); +} - public getSOAPNotes = catchAsync(async (req: RequestWithUser, res: Response): Promise => { - const clinicId = req.params.clinicId; - const patientId = req.params.patientId; +// to be added +/* - const notes = await this.medicalRecordService.getSOAPNotes(clinicId, patientId); +as getSOAPNotes gets json so we can use it for visit and history, +so it should accept these two types only - res.status(200).json({ - message: 'SOAP notes retrieved successfully', - data: notes, - }); - }); +another endpoint to add, get and delete file based records - // // metadata only - // public getRecordMetadata = catchAsync(async (req: Request, res: Response): Promise => { - // const record = await this.medicalRecordService.getRecordMetadata(req.params.id); - // res.status(200).json({ - // message: 'Medical record retrieved successfully', - // data: record, - // }); - // }); - - // // raw file stream - // public getRecordFile = catchAsync(async (req: Request, res: Response): Promise => { - // const record = await this.medicalRecordService.getRecordFile(req.params.id); - // res.setHeader('Content-Type', record.mime_type); - // res.setHeader('Content-Disposition', `inline; filename="${record.name}"`); - // res.status(200).send(record.buffer); - // }); -} \ No newline at end of file +add mock data if the blockchain netwrok is not available. +*/ \ No newline at end of file diff --git a/src/dtos/medical-records.dto.ts b/src/dtos/medical-records.dto.ts index 260b43a..e83d823 100644 --- a/src/dtos/medical-records.dto.ts +++ b/src/dtos/medical-records.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsEnum, IsOptional, IsUUID } from "class-validator"; +import { IsString, IsEnum, IsOptional, IsUUID, IsObject } from "class-validator"; import { RecordType } from "@/interfaces/enums.interface"; @@ -16,6 +16,18 @@ export class CreateMedicalRecordDto { } +// checks data when a doctor submits a JSON-based medical record +export class CreateDoctorRecordJsonDto { + @IsString() + name: string; + + @IsEnum(RecordType) + type: RecordType; + + @IsObject() + content: Record; +} + // permissions --> later // checks data when searching/filtering MR diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index 866427b..1208457 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -19,11 +19,12 @@ export enum Action { LOGOUT = 'LOGOUT', } +// should be changed to visit, histroy and file export enum RecordType { LAB_RESULT = 'LAB_RESULT', SCAN = 'SCAN', DIAGNOSIS = 'DIAGNOSIS', - VISIT_SUMMARY = 'VISIT_SUMMARY' + VISIT_SOAP = 'VISIT_SUMMARY' } export enum DOCTOR_FILES { diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 44cabf1..80d0096 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -13,7 +13,9 @@ const getAuthorization = (req: Request) => { if (cookie) return cookie; const header = req.header('Authorization'); - if (header) return header.split('Bearer ')[1]; + if (header) { + return header.startsWith('Bearer ') ? header.slice(7) : header; + } return null; }; diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 49173ae..016b907 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -1,26 +1,25 @@ -import { Routes } from "@/interfaces"; -import { ValidationMiddleware } from "@/middlewares/validation.middleware"; -import { Router } from "express"; -import { MedicalRecordController } from "@/controllers/medical-records.controller"; -import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; -import { Role } from "@prisma/client"; -import { CreateMedicalRecordDto } from "@/dtos/medical-records.dto"; -import { uploadSingleFile } from "@/middlewares/upload.middleware"; +import { Routes } from '@/interfaces'; +import { ValidationMiddleware } from '@/middlewares/validation.middleware'; +import { Router } from 'express'; +import { MedicalRecordController } from '@/controllers/medical-records.controller'; +import { AuthMiddleware, RoleMiddleware } from '@/middlewares/auth.middleware'; +import { Role } from '@prisma/client'; +import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { uploadSingleFile } from '@/middlewares/upload.middleware'; export class MedicalRecordRoute implements Routes { - public path = '/record'; - public router = Router(); - public medicalRecordController = new MedicalRecordController(); - - constructor() { - this.initializeRoutes(); - } - - private initializeRoutes() { - - this.router.get( - `${this.path}/health/ipfs`, - /* + public path = '/record'; + public router = Router(); + public medicalRecordController = new MedicalRecordController(); + + constructor() { + this.initializeRoutes(); + } + + private initializeRoutes() { + this.router.get( + `${this.path}/health/ipfs`, + /* #swagger.path = '/record/health/ipfs' #swagger.method = 'get' #swagger.tags = ['Medical Records'] @@ -33,12 +32,12 @@ export class MedicalRecordRoute implements Routes { description: 'IPFS service is unreachable' } */ - this.medicalRecordController.checkIpfsHealth - ); + this.medicalRecordController.checkIpfsHealth, + ); - this.router.get( - `${this.path}/patient`, - /* + this.router.get( + `${this.path}/patient/metadata`, + /* #swagger.path = '/record/patient' #swagger.method = 'get' #swagger.tags = ['Medical Records'] @@ -74,64 +73,18 @@ export class MedicalRecordRoute implements Routes { description: 'Unauthorized – missing or invalid token' } */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT), - this.medicalRecordController.getPatientMedicalRecords - ); - - this.router.get( - `${this.path}/:clinicId/:id`, - /* - #swagger.path = '/record/{clinicId}/{id}' - #swagger.method = 'get' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Downloads and decrypts a single medical record file. The clinicId identifies the caller\'s clinic for on-chain authorization.' - - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - - #swagger.parameters['clinicId'] = { - in: 'path', - description: 'UUID of the caller\'s clinic (used for on-chain access check)', - required: true, - type: 'string' - } - - #swagger.parameters['id'] = { - in: 'path', - description: 'UUID of the medical record', - required: true, - type: 'string' - } - - #swagger.responses[200] = { - description: 'Decrypted file returned as base64 with record metadata' - } - #swagger.responses[401] = { - description: 'Unauthorized – missing or invalid token' - } - #swagger.responses[403] = { - description: 'Access denied – clinic not authorized for this record' - } - #swagger.responses[404] = { - description: 'Record not found or already deleted' - } - */ - AuthMiddleware, - this.medicalRecordController.getRecordFile - ); - - this.router.post( - `${this.path}/:clinicId/:doctorId/upload`, - /* - #swagger.path = '/record/{clinicId}/{doctorId}/upload' + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.getRecordsMetadata, + ); + + this.router.post( + `${this.path}/:clinicId/:patientId/soap-note`, + /* + #swagger.path = '/record/{clinicId}/{patientId}/soap-note' #swagger.method = 'post' #swagger.tags = ['Medical Records'] - #swagger.description = 'Patient uploads a new medical record file. The clinic identity is used to store the encryption key on the blockchain.' + #swagger.description = 'Doctor creates a JSON-based medical record for a patient. Validates the doctor works at the clinic. Content is encrypted and stored on IPFS.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -142,78 +95,58 @@ export class MedicalRecordRoute implements Routes { #swagger.parameters['clinicId'] = { in: 'path', - description: 'UUID of the clinic whose Fabric identity will store the encryption key', + description: 'UUID of the clinic', required: true, type: 'string' } - #swagger.parameters['doctorId'] = { + #swagger.parameters['patientId'] = { in: 'path', - description: 'UUID of the doctor associated with this record', - required: true, - type: 'string' - } - - #swagger.parameters['file'] = { - in: 'formData', - description: 'The medical record file', - required: true, - type: 'file' - } - - #swagger.parameters['name'] = { - in: 'formData', - description: 'Display name for the record', + description: 'UUID of the patient', required: true, type: 'string' } - #swagger.parameters['type'] = { - in: 'formData', - description: 'Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)', + #swagger.parameters['body'] = { + in: 'body', + description: 'Medical record payload', required: true, - type: 'string' - } - - #swagger.parameters['appointmentId'] = { - in: 'formData', - description: 'UUID of the appointment (optional)', - required: false, - type: 'string' + schema: { + name: 'SOAP Note 2026-03-08', + type: 'SOAP_NOTE', + content: { + subjective: 'Patient reports headache', + objective: 'BP 120/80', + assessment: 'Tension headache', + plan: 'Ibuprofen 400mg' + } + } } #swagger.responses[201] = { - description: 'Medical record uploaded successfully', - schema: { - message: 'Medical record uploaded successfully' - } + description: 'Medical record created successfully', + schema: { message: 'Medical record created successfully', data: { recordId: 'uuid-string' } } } #swagger.responses[400] = { - description: 'No file uploaded or validation failed' - } - #swagger.responses[401] = { - description: 'Unauthorized – missing or invalid token' + description: 'Validation failed' } - #swagger.responses[404] = { - description: 'Patient encryption key not found' + #swagger.responses[403] = { + description: 'Doctor is not associated with this clinic' } */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT), - uploadSingleFile, - ValidationMiddleware(CreateMedicalRecordDto), - this.medicalRecordController.uploadRecord - ); - - - - this.router.delete( - `${this.path}/:clinicId/:id`, - /* - #swagger.path = '/record/{clinicId}/{id}' - #swagger.method = 'delete' + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + ValidationMiddleware(CreateDoctorRecordJsonDto), + this.medicalRecordController.addRecord, + ); + + this.router.get( + `${this.path}/patient/soap-notes`, + /* + #swagger.path = '/record/patient/soap-notes' + #swagger.method = 'get' #swagger.tags = ['Medical Records'] - #swagger.description = 'Soft-deletes a medical record. The clinicId identifies the caller\'s clinic; chaincode enforces owner-only deletion.' + #swagger.description = 'Patient retrieves all their own SOAP notes, authorized across all clinics on-chain.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -222,42 +155,25 @@ export class MedicalRecordRoute implements Routes { type: 'string' } - #swagger.parameters['clinicId'] = { - in: 'path', - description: 'UUID of the caller\'s clinic (must be the record owner)', - required: true, - type: 'string' - } - - #swagger.parameters['id'] = { - in: 'path', - description: 'UUID of the medical record to delete', - required: true, - type: 'string' - } - #swagger.responses[200] = { - description: 'Medical record deleted successfully', - schema: { message: 'Medical record deleted successfully' } + description: 'SOAP notes retrieved successfully', + schema: { + message: 'SOAP notes retrieved successfully', + data: [{ recordId: 'uuid-string', content: {} }] + } } #swagger.responses[401] = { description: 'Unauthorized – missing or invalid token' } - #swagger.responses[403] = { - description: 'Only the owner clinic can delete this record' - } - #swagger.responses[404] = { - description: 'Record not found or already deleted' - } */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT, Role.DOCTOR), - this.medicalRecordController.deleteRecord - ); - - this.router.post( - `${this.path}/grant-access`, - /* + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.getSOAPNotes, + ); + + this.router.post( + `${this.path}/grant-access`, + /* #swagger.path = '/record/grant-access' #swagger.method = 'post' #swagger.tags = ['Medical Records'] @@ -284,125 +200,24 @@ export class MedicalRecordRoute implements Routes { description: 'Unauthorized – missing or invalid token' } */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT), - this.medicalRecordController.grantAccess - ); - - this.router.post( - `${this.path}/:clinicId/:patientId/doctor-upload`, - /* - #swagger.path = '/record/{clinicId}/{patientId}/doctor-upload' - #swagger.method = 'post' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Doctor uploads a medical record for a patient. Validates the doctor works at the clinic. The clinic identity is used for blockchain operations.' - - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - - #swagger.parameters['clinicId'] = { - in: 'path', - description: 'UUID of the clinic', - required: true, - type: 'string' - } - - #swagger.parameters['patientId'] = { - in: 'path', - description: 'UUID of the patient', - required: true, - type: 'string' - } - - #swagger.parameters['file'] = { - in: 'formData', - description: 'The medical record file', - required: true, - type: 'file' - } - - #swagger.parameters['name'] = { - in: 'formData', - description: 'Display name for the record', - required: true, - type: 'string' - } - - #swagger.parameters['type'] = { - in: 'formData', - description: 'Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)', - required: true, - type: 'string' - } - - #swagger.responses[201] = { - description: 'Medical record uploaded successfully', - schema: { message: 'Medical record uploaded successfully', data: { recordId: 'uuid-string' } } - } - #swagger.responses[400] = { - description: 'No file uploaded or validation failed' - } - #swagger.responses[403] = { - description: 'Doctor is not associated with this clinic' - } - */ - AuthMiddleware, - RoleMiddleware(Role.DOCTOR), - uploadSingleFile, - ValidationMiddleware(CreateMedicalRecordDto), - this.medicalRecordController.addDoctorRecord - ); - - this.router.get( - `${this.path}/:clinicId/:patientId/soap-notes`, - /* - #swagger.path = '/record/{clinicId}/{patientId}/soap-notes' - #swagger.method = 'get' + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.grantAccess, + ); + + this.router.delete( + `${this.path}/dev/all`, + /* + #swagger.path = '/record/dev/all' + #swagger.method = 'delete' #swagger.tags = ['Medical Records'] - #swagger.description = 'Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, and returns their parsed contents. Requires on-chain authorization.' - - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - - #swagger.parameters['clinicId'] = { - in: 'path', - description: 'UUID of the caller\'s clinic (for on-chain access check)', - required: true, - type: 'string' - } - - #swagger.parameters['patientId'] = { - in: 'path', - description: 'UUID of the patient', - required: true, - type: 'string' - } - + #swagger.description = 'DEV ONLY — hard-deletes every medical record from DB, IPFS, and blockchain. No authentication required.' #swagger.responses[200] = { - description: 'SOAP notes retrieved successfully', - schema: { - message: 'SOAP notes retrieved successfully', - data: [{ recordId: 'uuid-string', content: {} }] - } - } - #swagger.responses[401] = { - description: 'Unauthorized – missing or invalid token' - } - #swagger.responses[403] = { - description: 'Access denied – clinic not authorized' + description: 'All records deleted', + schema: { message: 'Deleted 5 records from DB, IPFS, and blockchain', data: { deleted: 5 } } } */ - AuthMiddleware, - RoleMiddleware(Role.DOCTOR), - this.medicalRecordController.getSOAPNotes - ); - } -} \ No newline at end of file + this.medicalRecordController.deleteAllRecords, + ); + } +} diff --git a/src/server.ts b/src/server.ts index 48d8856..d2517c0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,8 +11,21 @@ import { QueueRoute } from './routes/queue.route'; import { NurseRoute } from './routes/nurse.route'; import { UsersRoute } from './routes/user.route'; import { MedicalRecordRoute } from './routes/medical-record.route'; +import { logger } from '@utils/logger'; + ValidateEnv(); +// Prevent the process from crashing on unhandled async errors +process.on('unhandledRejection', (reason: any) => { + logger.error(`⚠️ Unhandled Promise Rejection: ${reason?.message || reason}`); + if (reason?.stack) logger.error(reason.stack); +}); + +process.on('uncaughtException', (err: Error) => { + logger.error(`⚠️ Uncaught Exception: ${err.message}`); + if (err.stack) logger.error(err.stack); +}); + const app = new App( [ new AuthRoute(), new FabricRoute(), new AdminRoute(), diff --git a/src/services/encryption.service.ts b/src/services/encryption.service.ts index bce6add..c79a79d 100644 --- a/src/services/encryption.service.ts +++ b/src/services/encryption.service.ts @@ -68,6 +68,7 @@ export class EncryptionService { private getMasterKey(): Buffer { const masterKey = process.env.MASTER_ENCRYPTION_KEY; + console.log('Master Key:', masterKey); if (!masterKey) { const error = createBilingualError(500, ErrorMessages.MASTER_KEY_NOT_SET); throw new HttpException(error.status, error.message, error.messageAr); diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index 08aa4c8..37eff74 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -27,11 +27,21 @@ class FabricService { this.startCleanupInterval(); } - public async getGatewayConnection(clinicId: string): Promise { - const cached = this.connections.get(clinicId); - if (cached) { - cached.lastUsed = new Date(); - return cached; + public async getGatewayConnection(clinicId: string, forceNew = false): Promise { + if (forceNew) { + const stale = this.connections.get(clinicId); + if (stale) { + try { stale.gateway.close(); } catch (_) {} + try { stale.client.close(); } catch (_) {} + this.connections.delete(clinicId); + console.log(`🔄 Evicted stale connection for clinic: ${clinicId}`); + } + } else { + const cached = this.connections.get(clinicId); + if (cached) { + cached.lastUsed = new Date(); + return cached; + } } const identity = await identityStorage.getIdentity(clinicId); @@ -69,7 +79,9 @@ class FabricService { } private async newGrpcConnection(identity: FabricIdentity): Promise { - const tlsRootCert = Buffer.from(identity.tlsCertificate); + // gRPC requires the PEM to end with a newline — ensure it regardless of how it was stored + const tlsPem = identity.tlsCertificate.endsWith('\n') ? identity.tlsCertificate : identity.tlsCertificate + '\n'; + const tlsRootCert = Buffer.from(tlsPem); const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); return new grpc.Client(identity.peerEndpoint, tlsCredentials, { @@ -102,22 +114,18 @@ class FabricService { } public async storeRecordKey(clinicId: string, patientId: string, recordId: string, encryptedDEK: string): Promise { - const { contract, identity } = await this.getGatewayConnection(clinicId); + const { contract } = await this.getGatewayConnection(clinicId); console.log(`\n--> Submit Transaction: StoreRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); await contract.submit('StoreRecordKey', { arguments: [patientId, recordId], transientData: { encryptedDEK: Buffer.from(encryptedDEK) }, - endorsingOrganizations: [identity.mspId], }); } public async getRecordKey(clinicId: string, patientId: string, recordId: string): Promise { - const { contract, identity } = await this.getGatewayConnection(clinicId); + const { contract } = await this.getGatewayConnection(clinicId); console.log(`\n--> Evaluate Transaction: GetRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); - const resultBytes = await contract.evaluate('GetRecordKey', { - arguments: [patientId, recordId], - endorsingOrganizations: [identity.mspId], - }); + const resultBytes = await contract.evaluateTransaction('GetRecordKey', patientId, recordId); return this.utf8Decoder.decode(resultBytes); } @@ -137,37 +145,38 @@ class FabricService { } public async addRecord(clinicId: string, payload: MedicalRecord): Promise { - const { contract, identity } = await this.getGatewayConnection(clinicId); + const { contract } = await this.getGatewayConnection(clinicId); console.log(`\n--> Submit Transaction: AddRecord (clinic: ${clinicId})`); await contract.submit('AddRecord', { arguments: [payload.patientId, payload.recordId, payload.doctorId, payload.type], transientData: { - ipfsCidKey: Buffer.from(payload.ipfsCidKey), + ipfsCid: Buffer.from(payload.ipfsCidKey), }, - endorsingOrganizations: [identity.mspId], }); } - public async getRecordsByPatient(clinicId: string, patientId: string): Promise { - const { contract, identity } = await this.getGatewayConnection(clinicId); + public async getRecordsByPatient(clinicId: string, patientId: string, retry = true): Promise { + const { contract } = await this.getGatewayConnection(clinicId); console.log(`\n--> Evaluate Transaction: GetRecordsByPatient (clinic: ${clinicId}, patient: ${patientId})`); try { - // Evaluate on owner's peers if the caller is not the owner; we pass endorsingOrganizations - // as the caller's own MSP so the peer can reach into its implicit private data collection. - const resultBytes = await contract.evaluate('GetRecordsByPatient', { - arguments: [patientId], - endorsingOrganizations: [identity.mspId], - }); + const resultBytes = await contract.evaluateTransaction('GetRecordsByPatient', patientId); const resultJson = this.utf8Decoder.decode(resultBytes); return JSON.parse(resultJson) as MedicalRecord[]; - } catch (err: any) { - const msg = err?.message || String(err); + } catch (err: unknown) { + const msg = (err instanceof Error ? err.message : String(err)) || ''; + console.error(`❌ GetRecordsByPatient error for clinic ${clinicId}:`, err); if (msg.toLowerCase().includes('not authorized')) { throw new HttpException(403, `Access denied for clinic ${clinicId} to records of patient ${patientId}`, msg); } + // ABORTED (gRPC code 10) usually means the channel is stale — evict and retry once + if (retry && (msg.includes('ABORTED') || msg.includes('10 ABORTED'))) { + console.warn(`⚠️ ABORTED on GetRecordsByPatient for clinic ${clinicId}, retrying with fresh connection...`); + await this.getGatewayConnection(clinicId, true); + return this.getRecordsByPatient(clinicId, patientId, false); + } throw err; } } @@ -185,13 +194,12 @@ class FabricService { const transientData: Record = {}; if (payload.ipfsCidKey) { - transientData.ipfsCidKey = Buffer.from(payload.ipfsCidKey); + transientData.ipfsCid = Buffer.from(payload.ipfsCidKey); } await contract.submit('UpdateRecord', { arguments: [patientId, payload.recordId, payload.doctorId, payload.type], ...(Object.keys(transientData).length > 0 ? { transientData } : {}), - endorsingOrganizations: [identity.mspId], }); } diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 3bdc90c..3714d92 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -1,5 +1,5 @@ import { HttpException } from '@/exceptions/HttpException'; -import { CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; import { MedicalRecord, MedicalRecordFile } from '@/interfaces/medicalRecords.interface'; import prisma from '@/config/prisma'; import { Prisma } from '@prisma/client'; @@ -199,18 +199,15 @@ export class MedicalRecordService { } /** - * Doctor-initiated record creation. - * Validates the doctor works in the clinic, encrypts the file, uploads to IPFS, - * stores in DB, syncs to blockchain, and returns the record ID. + * Doctor-initiated record creation (JSON-based). + * Validates the doctor works in the clinic, serialises the JSON content to a Buffer, + * encrypts it, uploads to IPFS, stores in DB, syncs to blockchain, and returns the record ID. */ public async addDoctorRecord( clinicId: string, patientId: string, doctorId: string, - fileData: CreateMedicalRecordDto, - fileBuffer: Buffer, - fileName: string, - mimeType: string, + dto: CreateDoctorRecordJsonDto, ): Promise { // Validate the doctor is associated with this clinic const clinicDoctor = await prisma.clinicDoctor.findUnique({ @@ -222,11 +219,16 @@ export class MedicalRecordService { const recordId = randomUUID(); + // Serialise JSON content to a UTF-8 Buffer and encrypt it + const contentBuffer = Buffer.from(JSON.stringify(dto.content), 'utf-8'); const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); - const encryptedFile = this.encryptionService.encryptFile(fileBuffer, recordDEK); + const encryptedFile = this.encryptionService.encryptFile(contentBuffer, recordDEK); recordDEK.fill(0); - const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + // Upload as octet-stream — the file is encrypted binary regardless of original content type. + // Uploading as application/json causes Pinata's gateway to call .json() on the encrypted + // bytes when fetching, which throws a parse error. + const cid = await this.ipfsService.uploadFile(encryptedFile, `${recordId}.enc`, 'application/octet-stream'); await prisma.medicalRecord.create({ data: { @@ -234,10 +236,10 @@ export class MedicalRecordService { patient_id: patientId, doctor_id: doctorId, clinic_id: clinicId, - name: fileData.name, + name: dto.name, cid: cid, - type: fileData.type, - mime_type: mimeType, + type: dto.type, + mime_type: 'application/json', // logical type of the decrypted content } as Prisma.MedicalRecordUncheckedCreateInput, }); @@ -246,7 +248,7 @@ export class MedicalRecordService { patientId, recordId, doctorId, - type: fileData.type, + type: dto.type, ipfsCidKey: cid, }); @@ -257,11 +259,51 @@ export class MedicalRecordService { * Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, * and returns their parsed contents as a list of objects. */ + /** + * Doctor-facing: returns DB metadata for all of a patient's records + * that the caller's clinic is authorized to access on-chain. + */ + public async getPatientRecordsForDoctor(callerClinicId: string, patientId: string): Promise { + // Get on-chain authorized record IDs for this clinic + const authorizedOnChain = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); + const authorizedIds = new Set(authorizedOnChain.map(r => r.recordId)); + + const records = await prisma.medicalRecord.findMany({ + where: { patient_id: patientId, deleted_at: null }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + return records + .filter(r => authorizedIds.has(r.id)) + .map(record => ({ + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, + name: record.name, + type: record.type, + cid: record.cid, + mime_type: record.mime_type, + })); + } + public async getSOAPNotes(callerClinicId: string, patientId: string): Promise> { const records = await prisma.medicalRecord.findMany({ where: { patient_id: patientId, - type: 'SOAP_NOTE', + mime_type: 'application/json', deleted_at: null, }, select: { @@ -282,15 +324,97 @@ export class MedicalRecordService { for (const record of records) { if (!authorizedIds.has(record.id)) continue; - const encryptedFile = await this.ipfsService.getFile(record.cid); - const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); - const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); - recordDEK.fill(0); + try { + const encryptedFile = await this.ipfsService.getFile(record.cid); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); + + const content = JSON.parse(decryptedFile.toString('utf-8')); + results.push({ recordId: record.id, content }); + } catch (e) { + // Skip records that are binary files (not JSON-based) + console.warn(`⚠️ Skipping record ${record.id}: not a JSON record (${e.message})`); + } + } + + return results; + } + public async getSOAPNotesForPatient(patientId: string): Promise> { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + mime_type: 'application/json', + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + // Get all distinct clinics from the records + const distinctClinicIds = [...new Set(records.map(r => r.clinic_id))]; + + // Call getRecordsByPatient for each clinic sequentially to avoid concurrent gRPC channel conflicts + const authorizedIds = new Set(); + for (const clinicId of distinctClinicIds) { + const authorizedRecords = await this.fabricService.getRecordsByPatient(clinicId, patientId); + authorizedRecords.forEach(r => authorizedIds.add(r.recordId)); + } + + const results: Array<{ recordId: string; content: any }> = []; - const content = JSON.parse(decryptedFile.toString('utf-8')); - results.push({ recordId: record.id, content }); + for (const record of records) { + if (!authorizedIds.has(record.id)) continue; + + try { + const encryptedFile = await this.ipfsService.getFile(record.cid); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); + + const content = JSON.parse(decryptedFile.toString('utf-8')); + results.push({ recordId: record.id, content }); + } catch (e) { + // Skip records that are binary files (not JSON-based) + console.warn(`⚠️ Skipping record ${record.id}: not a JSON record (${e.message})`); + } } return results; } + + /** + * DEV ONLY — hard-deletes every medical record from DB, IPFS, and the blockchain. + */ + public async deleteAllRecords(): Promise<{ deleted: number }> { + const records = await prisma.medicalRecord.findMany({ + select: { id: true, patient_id: true, clinic_id: true, cid: true }, + }); + + for (const record of records) { + // 1. Remove from blockchain + try { + await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, record.id); + } catch (e) { + console.warn(`⚠️ Chain delete skipped for ${record.id}: ${e.message}`); + } + + // 2. Remove from IPFS + try { + await this.ipfsService.deleteFile(record.cid); + } catch (e) { + console.warn(`⚠️ IPFS delete skipped for ${record.id}: ${e.message}`); + } + + // 3. Hard-delete from DB + await prisma.medicalRecord.delete({ where: { id: record.id } }); + } + + return { deleted: records.length }; + } } \ No newline at end of file diff --git a/src/swagger-output.json b/src/swagger-output.json index 6b9ef65..63a4511 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -52,6 +52,14 @@ "schemes": [ "http" ], + "securityDefinitions": { + "bearerAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "Enter your Bearer token: Bearer " + } + }, "paths": { "/auth/signup": { "post": { @@ -8651,26 +8659,26 @@ } } }, - "/record/{clinicId}/{id}": { - "get": { + "/record/{clinicId}/{patientId}/soap-note": { + "post": { "tags": [ "Medical Records" ], - "description": "Downloads and decrypts a single medical record file. The clinicId identifies the caller\\'s clinic for on-chain authorization.", + "description": "Doctor creates a JSON-based medical record for a patient. Validates the doctor works at the clinic. Content is encrypted and stored on IPFS.", "parameters": [ { "name": "clinicId", "in": "path", "required": true, "type": "string", - "description": "UUID of the caller's clinic (used for on-chain access check)" + "description": "UUID of the clinic" }, { - "name": "id", + "name": "patientId", "in": "path", "required": true, "type": "string", - "description": "UUID of the medical record" + "description": "UUID of the patient" }, { "name": "Authorization", @@ -8678,43 +8686,89 @@ "description": "Bearer token for authentication", "required": true, "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Medical record payload", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "SOAP Note 2026-03-08" + }, + "type": { + "type": "string", + "example": "SOAP_NOTE" + }, + "content": { + "type": "object", + "properties": { + "subjective": { + "type": "string", + "example": "Patient reports headache" + }, + "objective": { + "type": "string", + "example": "BP 120/80" + }, + "assessment": { + "type": "string", + "example": "Tension headache" + }, + "plan": { + "type": "string", + "example": "Ibuprofen 400mg" + } + } + } + } + } } ], "responses": { - "200": { - "description": "Decrypted file returned as base64 with record metadata" + "201": { + "description": "Medical record created successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical record created successfully" + }, + "data": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + } + } + } + }, + "xml": { + "name": "main" + } + } }, - "401": { - "description": "Unauthorized – missing or invalid token" + "400": { + "description": "Validation failed" }, "403": { - "description": "Access denied – clinic not authorized for this record" - }, - "404": { - "description": "Record not found or already deleted" + "description": "Doctor is not associated with this clinic" } } - }, - "delete": { + } + }, + "/record/patient/soap-notes": { + "get": { "tags": [ "Medical Records" ], - "description": "Soft-deletes a medical record. The clinicId identifies the caller\\'s clinic; chaincode enforces owner-only deletion.", + "description": "Patient retrieves all their own SOAP notes, authorized across all clinics on-chain.", "parameters": [ - { - "name": "clinicId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the caller's clinic (must be the record owner)" - }, - { - "name": "id", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the medical record to delete" - }, { "name": "Authorization", "in": "cookie", @@ -8725,13 +8779,29 @@ ], "responses": { "200": { - "description": "Medical record deleted successfully", + "description": "SOAP notes retrieved successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical record deleted successfully" + "example": "SOAP notes retrieved successfully" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + }, + "content": { + "type": "object", + "properties": {} + } + } + } } }, "xml": { @@ -8741,36 +8811,29 @@ }, "401": { "description": "Unauthorized – missing or invalid token" - }, - "403": { - "description": "Only the owner clinic can delete this record" - }, - "404": { - "description": "Record not found or already deleted" } } } }, - "/record/{clinicId}/{doctorId}/upload": { - "post": { + "/record/{clinicId}/{recordId}": { + "delete": { "tags": [ "Medical Records" ], - "description": "Patient uploads a new medical record file. The clinic identity is used to store the encryption key on the blockchain.", + "description": "Soft-deletes a medical record. The clinicId identifies the caller\\'s clinic; chaincode enforces owner-only deletion.", "parameters": [ { "name": "clinicId", "in": "path", "required": true, "type": "string", - "description": "UUID of the clinic whose Fabric identity will store the encryption key" + "description": "UUID of the caller's clinic (must be the record owner)" }, { - "name": "doctorId", + "name": "recordId", "in": "path", "required": true, - "type": "string", - "description": "UUID of the doctor associated with this record" + "type": "string" }, { "name": "Authorization", @@ -8780,43 +8843,22 @@ "type": "string" }, { - "name": "file", - "in": "formData", - "description": "The medical record file", - "required": true, - "type": "file" - }, - { - "name": "name", - "in": "formData", - "description": "Display name for the record", - "required": true, - "type": "string" - }, - { - "name": "type", - "in": "formData", - "description": "Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)", + "name": "id", + "in": "path", + "description": "UUID of the medical record to delete", "required": true, "type": "string" - }, - { - "name": "appointmentId", - "in": "formData", - "description": "UUID of the appointment (optional)", - "required": false, - "type": "string" } ], "responses": { - "201": { - "description": "Medical record uploaded successfully", + "200": { + "description": "Medical record deleted successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical record uploaded successfully" + "example": "Medical record deleted successfully" } }, "xml": { @@ -8824,14 +8866,14 @@ } } }, - "400": { - "description": "No file uploaded or validation failed" - }, "401": { "description": "Unauthorized – missing or invalid token" }, + "403": { + "description": "Only the owner clinic can delete this record" + }, "404": { - "description": "Patient encryption key not found" + "description": "Record not found or already deleted" } } } @@ -8887,142 +8929,28 @@ } } }, - "/record/{clinicId}/{patientId}/doctor-upload": { - "post": { + "/record/dev/all": { + "delete": { "tags": [ "Medical Records" ], - "description": "Doctor uploads a medical record for a patient. Validates the doctor works at the clinic. The clinic identity is used for blockchain operations.", - "parameters": [ - { - "name": "clinicId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the clinic" - }, - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the patient" - }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - }, - { - "name": "file", - "in": "formData", - "description": "The medical record file", - "required": true, - "type": "file" - }, - { - "name": "name", - "in": "formData", - "description": "Display name for the record", - "required": true, - "type": "string" - }, - { - "name": "type", - "in": "formData", - "description": "Record type enum (LAB_RESULT | SCAN | DIAGNOSIS | VISIT_SUMMARY | SOAP_NOTE | MEDICAL_HISTORY)", - "required": true, - "type": "string" - } - ], + "description": "DEV ONLY — hard-deletes every medical record from DB, IPFS, and blockchain. No authentication required.", "responses": { - "201": { - "description": "Medical record uploaded successfully", + "200": { + "description": "All records deleted", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical record uploaded successfully" + "example": "Deleted 5 records from DB, IPFS, and blockchain" }, "data": { "type": "object", "properties": { - "recordId": { - "type": "string", - "example": "uuid-string" - } - } - } - }, - "xml": { - "name": "main" - } - } - }, - "400": { - "description": "No file uploaded or validation failed" - }, - "403": { - "description": "Doctor is not associated with this clinic" - } - } - } - }, - "/record/{clinicId}/{patientId}/soap-notes": { - "get": { - "tags": [ - "Medical Records" - ], - "description": "Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, and returns their parsed contents. Requires on-chain authorization.", - "parameters": [ - { - "name": "clinicId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the caller's clinic (for on-chain access check)" - }, - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the patient" - }, - { - "name": "Authorization", - "in": "cookie", - "description": "Bearer token for authentication", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "SOAP notes retrieved successfully", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "SOAP notes retrieved successfully" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "recordId": { - "type": "string", - "example": "uuid-string" - }, - "content": { - "type": "object", - "properties": {} - } + "deleted": { + "type": "number", + "example": 5 } } } @@ -9031,15 +8959,14 @@ "name": "main" } } - }, - "401": { - "description": "Unauthorized – missing or invalid token" - }, - "403": { - "description": "Access denied – clinic not authorized" } } } } - } + }, + "security": [ + { + "bearerAuth": [] + } + ] } \ No newline at end of file diff --git a/src/swagger.mjs b/src/swagger.mjs index 6fd47b3..176e404 100644 --- a/src/swagger.mjs +++ b/src/swagger.mjs @@ -7,6 +7,15 @@ const doc = { }, host: 'localhost:3000', schemes: ['http'], + securityDefinitions: { + bearerAuth: { + type: 'apiKey', + in: 'header', + name: 'Authorization', + description: 'Enter your Bearer token: Bearer ', + }, + }, + security: [{ bearerAuth: [] }], tags: [ { name: 'Auth', description: 'Authentication and account endpoints' }, { name: 'Admin', description: 'Admin endpoints' }, @@ -25,15 +34,15 @@ const outputFile = './swagger-output.json'; const endpointsFiles = [ './routes/auth.route.ts', './routes/fabric.route.ts', - './src/routes/admin.route.ts', - './src/routes/superAdmin.route.ts', - './src/routes/doctors.route.ts', - './src/routes/clinic.route.ts', - './src/routes/appointment.route.ts', - './src/routes/queue.route.ts', - './src/routes/user.route.ts', - './src/routes/nurse.route.ts', - './src/routes/medical-record.route.ts' + './routes/admin.route.ts', + './routes/superAdmin.route.ts', + './routes/doctors.route.ts', + './routes/clinic.route.ts', + './routes/appointment.route.ts', + './routes/queue.route.ts', + './routes/user.route.ts', + './routes/nurse.route.ts', + './routes/medical-record.route.ts' ]; swaggerAutogen()(outputFile, endpointsFiles, doc); From d469181019d8d1b54b7578d746ee19e8c5d2110e Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 9 Mar 2026 19:54:06 +0200 Subject: [PATCH 193/210] fix auth: return role for socket service --- src/interfaces/auth.interface.ts | 3 +++ src/services/auth.service.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interfaces/auth.interface.ts b/src/interfaces/auth.interface.ts index 5dfe9e3..9ac00f2 100644 --- a/src/interfaces/auth.interface.ts +++ b/src/interfaces/auth.interface.ts @@ -1,8 +1,11 @@ import { Request } from 'express'; import { User } from '@interfaces/users.interface'; +import { Role } from '@prisma/client'; + export interface DataStoredInToken { id: string; + role:Role; } export interface AccessTokenData { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 62d67fb..e764389 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -166,7 +166,7 @@ export class AuthService { } public createAccessToken(user: Partial): AccessTokenData { - const dataStoredInToken: DataStoredInToken = { id: user.id }; + const dataStoredInToken: DataStoredInToken = { id: user.id , role: user.role}; const secretKey: string = SECRET_KEY; const expiresIn: number = this.parseTimeToSeconds(ACCESS_TOKEN_EXPIRY); @@ -174,7 +174,7 @@ export class AuthService { } public async createRefreshToken(user: Partial): Promise { - const dataStoredInToken: DataStoredInToken = { id: user.id }; + const dataStoredInToken: DataStoredInToken = { id: user.id , role: user.role}; const secretKey: string = REFRESH_TOKEN_SECRET; const expiresIn: number = this.parseTimeToSeconds(REFRESH_TOKEN_EXPIRY); From b3bf5c33f7114b97422b03fb018dfc16d1fff95d Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 9 Mar 2026 20:35:58 +0200 Subject: [PATCH 194/210] Fix: daily schedule of the doctor --- src/services/appointment.service.ts | 1 - src/services/socket.service.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index a2f78ba..f199cb1 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -832,7 +832,6 @@ export class AppointmentService { gte: startOfDay, lte: endOfDay }, - deleted_at: null, }, select: { id: true, diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 43223ea..79bacfa 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -143,7 +143,7 @@ export class SocketService { private async sendInitialPatientData(patientId: string): Promise { try { - const appointments = await this.appointmentService.getPatientAppointments(patientId); + const appointments = await this.appointmentService.getTodayAppointment(patientId) ?? []; const appointmentsWithQueue = await Promise.all(appointments.map(async (app) => { await this.queueService.calculateQueuePosition(app.id); const queuePosition = await this.queueService.getQueuePosition(app.id); From 6903384f9a5bbf4d9c237746f784320795e65ddc Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 9 Mar 2026 20:47:24 +0200 Subject: [PATCH 195/210] fix: socket service data handling --- src/services/socket.service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index 79bacfa..1bcdb72 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -95,6 +95,11 @@ export class SocketService { message: 'Successfully connected to socket server', userId: userId }); + socket.on('request_initial_data', () => { + if (userRole === 'PATIENT') this.sendInitialPatientData(userId); + else if (userRole === 'DOCTOR') this.sendInitialDoctorData(userId); + else if (userRole === 'NURSE') this.sendInitialNurseData(userId); + }); if (userRole === 'PATIENT') { this.sendInitialPatientData(userId); From b5e5895a7d06ea01c6c8e116d9ae7e8a9355f506 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 13 Mar 2026 00:47:59 +0200 Subject: [PATCH 196/210] finish medical records endpoints --- src/controllers/medical-records.controller.ts | 102 +- src/dtos/medical-records.dto.ts | 20 + src/interfaces/enums.interface.ts | 6 +- src/interfaces/index.ts | 4 +- .../20260312145020_record_type/migration.sql | 10 + src/prisma/schema.prisma | 2 + src/routes/medical-record.route.ts | 271 ++++-- src/services/identity-storage.service.ts | 11 +- src/services/medical-records.service.ts | 874 ++++++++++-------- src/swagger-output.json | 447 ++++++--- 10 files changed, 1173 insertions(+), 574 deletions(-) create mode 100644 src/prisma/migrations/20260312145020_record_type/migration.sql diff --git a/src/controllers/medical-records.controller.ts b/src/controllers/medical-records.controller.ts index d82fafd..8d37f1e 100644 --- a/src/controllers/medical-records.controller.ts +++ b/src/controllers/medical-records.controller.ts @@ -1,21 +1,21 @@ -import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto, CreatePatientMedicalHistoryDto, UpdatePatientMedicalHistoryDto } from '@/dtos/medical-records.dto'; import { Request, Response } from 'express'; import { RequestWithUser } from '@/interfaces/auth.interface'; import { MedicalRecordService } from '@/services/medical-records.service'; import { catchAsync } from '@/utils/catchAsync'; - +import { RecordType } from '@/interfaces'; export class MedicalRecordController { private medicalRecordService = new MedicalRecordService(); - public checkIpfsHealth = catchAsync(async (req: Request, res: Response): Promise => { + public getIpfsHealth = catchAsync(async (req: Request, res: Response): Promise => { const result = await this.medicalRecordService.checkIpfsHealth(); res.status(200).json(result); }); - public getRecordsMetadata = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getPatientRecordsMetadata = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; const records = await this.medicalRecordService.getPatientFiles(patientId); @@ -26,8 +26,7 @@ export class MedicalRecordController { }); }); - // this function adds json based data only - public addRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public createDoctorRecord = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const clinicId = req.params.clinicId; const patientId = req.params.patientId; const doctorId = req.user.id; @@ -46,18 +45,88 @@ export class MedicalRecordController { }); }); - public getSOAPNotes = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public createPatientMedicalHistory = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const dto: CreatePatientMedicalHistoryDto = req.body; + + const recordId = await this.medicalRecordService.addPatientMedicalHistory(patientId, dto); + + res.status(201).json({ + message: 'Medical history entry created successfully', + data: { recordId }, + }); + }); + + public updatePatientMedicalHistory = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const recordId = req.params.recordId; + const dto: UpdatePatientMedicalHistoryDto = req.body; + + await this.medicalRecordService.updatePatientMedicalHistory(patientId, recordId, dto); + + res.status(200).json({ + message: 'Medical history entry updated successfully', + }); + }); + + public deletePatientMedicalHistory = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + const recordId = req.params.recordId; + + await this.medicalRecordService.deletePatientMedicalHistory(patientId, recordId); + + res.status(200).json({ + message: 'Medical history entry deleted successfully', + }); + }); + + public getPatientVisitSummaries = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const patientId = req.user.id; + + const notes = await this.medicalRecordService.getSOAPNotesForPatient(patientId, RecordType.VISIT_SUMMARY); + + res.status(200).json({ + message: 'Visit summaries retrieved successfully', + data: notes, + }); + }); + + public getPatientMedicalHistory = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; - const notes = await this.medicalRecordService.getSOAPNotesForPatient(patientId); + const history = await this.medicalRecordService.getMedicalHistory(patientId); + + res.status(200).json({ + message: 'Medical history retrieved successfully', + data: history, + }); + }); + + public getDoctorPatientVisitSummaries = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const patientId = req.params.patientId; + + const notes = await this.medicalRecordService.getVisitSummariesForDoctor(doctorId, patientId); res.status(200).json({ - message: 'SOAP notes retrieved successfully', + message: 'Visit summaries retrieved successfully', data: notes, }); }); - public grantAccess = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + public getDoctorPatientMedicalHistory = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const patientId = req.params.patientId; + + const history = await this.medicalRecordService.getMedicalHistoryForDoctor(doctorId, patientId); + + res.status(200).json({ + message: 'Medical history retrieved successfully', + data: history, + }); + }); + + public grantPatientAccess = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const patientId = req.user.id; const { targetClinicId } = req.body; @@ -67,7 +136,7 @@ export class MedicalRecordController { message: 'Access granted successfully', }); }); - // DEV ONLY — no auth + public deleteAllRecords = catchAsync(async (_req: Request, res: Response): Promise => { const result = await this.medicalRecordService.deleteAllRecords(); res.status(200).json({ @@ -76,14 +145,3 @@ export class MedicalRecordController { }); }); } - -// to be added -/* - -as getSOAPNotes gets json so we can use it for visit and history, -so it should accept these two types only - -another endpoint to add, get and delete file based records - -add mock data if the blockchain netwrok is not available. -*/ \ No newline at end of file diff --git a/src/dtos/medical-records.dto.ts b/src/dtos/medical-records.dto.ts index e83d823..6518e32 100644 --- a/src/dtos/medical-records.dto.ts +++ b/src/dtos/medical-records.dto.ts @@ -28,6 +28,26 @@ export class CreateDoctorRecordJsonDto { content: Record; } +// checks data when a patient submits their own medical history entry +export class CreatePatientMedicalHistoryDto { + @IsString() + name: string; + + @IsObject() + content: Record; +} + +// checks data when a patient updates an existing medical history entry +export class UpdatePatientMedicalHistoryDto { + @IsOptional() + @IsString() + name?: string; + + @IsOptional() + @IsObject() + content?: Record; +} + // permissions --> later // checks data when searching/filtering MR diff --git a/src/interfaces/enums.interface.ts b/src/interfaces/enums.interface.ts index 1208457..7336ec8 100644 --- a/src/interfaces/enums.interface.ts +++ b/src/interfaces/enums.interface.ts @@ -24,7 +24,11 @@ export enum RecordType { LAB_RESULT = 'LAB_RESULT', SCAN = 'SCAN', DIAGNOSIS = 'DIAGNOSIS', - VISIT_SOAP = 'VISIT_SUMMARY' + VISIT_SUMMARY = 'VISIT_SUMMARY', + SOAP_NOTE = 'SOAP_NOTE', + MEDICAL_HISTORY = 'MEDICAL_HISTORY', + FILE = 'FILE', + VISIT = 'VISIT', } export enum DOCTOR_FILES { diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 52bad34..774949e 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -29,7 +29,7 @@ export * from './audit-logs.interface'; export * from './medical-records.interface'; // Appointments -export * from './appointments.interface'; +// export * from './appointments.interface'; // Doctor Schedule -export * from './doctor-schedule.interface'; +// export * from './doctor-schedule.interface'; diff --git a/src/prisma/migrations/20260312145020_record_type/migration.sql b/src/prisma/migrations/20260312145020_record_type/migration.sql new file mode 100644 index 0000000..f81b2b8 --- /dev/null +++ b/src/prisma/migrations/20260312145020_record_type/migration.sql @@ -0,0 +1,10 @@ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "RecordType" ADD VALUE 'VISIT'; +ALTER TYPE "RecordType" ADD VALUE 'FILE'; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 1ff9e1d..7c73d9e 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -482,6 +482,8 @@ enum RecordType { VISIT_SUMMARY SOAP_NOTE MEDICAL_HISTORY + VISIT + FILE } enum DoctorAccountStatus { diff --git a/src/routes/medical-record.route.ts b/src/routes/medical-record.route.ts index 016b907..5b7659b 100644 --- a/src/routes/medical-record.route.ts +++ b/src/routes/medical-record.route.ts @@ -4,11 +4,16 @@ import { Router } from 'express'; import { MedicalRecordController } from '@/controllers/medical-records.controller'; import { AuthMiddleware, RoleMiddleware } from '@/middlewares/auth.middleware'; import { Role } from '@prisma/client'; -import { CreateDoctorRecordJsonDto, CreateMedicalRecordDto } from '@/dtos/medical-records.dto'; +import { + CreateDoctorRecordJsonDto, + CreateMedicalRecordDto, + CreatePatientMedicalHistoryDto, + UpdatePatientMedicalHistoryDto, +} from '@/dtos/medical-records.dto'; import { uploadSingleFile } from '@/middlewares/upload.middleware'; export class MedicalRecordRoute implements Routes { - public path = '/record'; + public path = '/medical-records'; public router = Router(); public medicalRecordController = new MedicalRecordController(); @@ -20,9 +25,9 @@ export class MedicalRecordRoute implements Routes { this.router.get( `${this.path}/health/ipfs`, /* - #swagger.path = '/record/health/ipfs' + #swagger.path = '/medical-records/health/ipfs' #swagger.method = 'get' - #swagger.tags = ['Medical Records'] + #swagger.tags = ['Medical Records - Public'] #swagger.description = 'Checks connectivity to the IPFS (Pinata) service' #swagger.responses[200] = { description: 'IPFS connection is healthy', @@ -32,58 +37,15 @@ export class MedicalRecordRoute implements Routes { description: 'IPFS service is unreachable' } */ - this.medicalRecordController.checkIpfsHealth, - ); - - this.router.get( - `${this.path}/patient/metadata`, - /* - #swagger.path = '/record/patient' - #swagger.method = 'get' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Retrieves all medical record metadata for the authenticated patient (no file bytes)' - - #swagger.parameters['Authorization'] = { - in: 'cookie', - description: 'Bearer token for authentication', - required: true, - type: 'string' - } - - #swagger.responses[200] = { - description: 'Medical records retrieved successfully', - schema: { - message: 'Medical records retrieved successfully', - data: [ - { - id: 'uuid-string', - patient_id: 'uuid-string', - clinic_id: 'uuid-string', - doctor_id: 'uuid-string', - appointment_id: 'uuid-string', - name: 'Blood Test Results', - type: 'LAB_RESULT', - mime_type: 'application/pdf', - cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' - } - ] - } - } - #swagger.responses[401] = { - description: 'Unauthorized – missing or invalid token' - } - */ - AuthMiddleware, - RoleMiddleware(Role.PATIENT), - this.medicalRecordController.getRecordsMetadata, + this.medicalRecordController.getIpfsHealth, ); this.router.post( - `${this.path}/:clinicId/:patientId/soap-note`, + `${this.path}/clinics/:clinicId/patients/:patientId/visit-summaries`, /* - #swagger.path = '/record/{clinicId}/{patientId}/soap-note' + #swagger.path = '/medical-records/clinics/{clinicId}/patients/{patientId}/visit-summaries' #swagger.method = 'post' - #swagger.tags = ['Medical Records'] + #swagger.tags = ['Medical Records - Doctor'] #swagger.description = 'Doctor creates a JSON-based medical record for a patient. Validates the doctor works at the clinic. Content is encrypted and stored on IPFS.' #swagger.parameters['Authorization'] = { @@ -137,16 +99,16 @@ export class MedicalRecordRoute implements Routes { AuthMiddleware, RoleMiddleware(Role.DOCTOR), ValidationMiddleware(CreateDoctorRecordJsonDto), - this.medicalRecordController.addRecord, + this.medicalRecordController.createDoctorRecord, ); this.router.get( - `${this.path}/patient/soap-notes`, + `${this.path}/patient/visit-summaries`, /* - #swagger.path = '/record/patient/soap-notes' + #swagger.path = '/medical-records/patient/visit-summaries' #swagger.method = 'get' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Patient retrieves all their own SOAP notes, authorized across all clinics on-chain.' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient retrieves all their VISIT_SUMMARY records, decrypted and authorized across all clinics on-chain.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -156,9 +118,9 @@ export class MedicalRecordRoute implements Routes { } #swagger.responses[200] = { - description: 'SOAP notes retrieved successfully', + description: 'Visit summaries retrieved successfully', schema: { - message: 'SOAP notes retrieved successfully', + message: 'Visit summaries retrieved successfully', data: [{ recordId: 'uuid-string', content: {} }] } } @@ -168,16 +130,197 @@ export class MedicalRecordRoute implements Routes { */ AuthMiddleware, RoleMiddleware(Role.PATIENT), - this.medicalRecordController.getSOAPNotes, + this.medicalRecordController.getPatientVisitSummaries, + ); + + this.router.post( + `${this.path}/patient/medical-history`, + /* + #swagger.path = '/medical-records/patient/medical-history' + #swagger.method = 'post' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient adds a new MEDICAL_HISTORY entry. Type is fixed — only name and content are required.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + name: 'Previous Surgeries', + content: { conditions: ['hypertension'], surgeries: ['appendectomy'] } + } + } + + #swagger.responses[201] = { + description: 'Medical history entry created successfully', + schema: { message: 'Medical history entry created successfully', data: { recordId: 'uuid-string' } } + } + #swagger.responses[400] = { description: 'Validation failed' } + #swagger.responses[401] = { description: 'Unauthorized' } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + ValidationMiddleware(CreatePatientMedicalHistoryDto), + this.medicalRecordController.createPatientMedicalHistory, + ); + + this.router.patch( + `${this.path}/patient/medical-history/:recordId`, + /* + #swagger.path = '/medical-records/patient/medical-history/{recordId}' + #swagger.method = 'patch' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient updates an existing MEDICAL_HISTORY record they own. At least one of name or content must be provided. If content changes, the file is re-encrypted and re-uploaded to IPFS.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['recordId'] = { + in: 'path', + description: 'UUID of the record to update', + required: true, + type: 'string' + } + + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + name: 'Updated History Title', + content: { conditions: ['hypertension'], surgeries: ['appendectomy'] } + } + } + + #swagger.responses[200] = { + description: 'Medical history entry updated successfully', + schema: { message: 'Medical history entry updated successfully' } + } + #swagger.responses[400] = { description: 'Validation failed' } + #swagger.responses[401] = { description: 'Unauthorized' } + #swagger.responses[404] = { description: 'Record not found or not owned by patient' } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + ValidationMiddleware(UpdatePatientMedicalHistoryDto), + this.medicalRecordController.updatePatientMedicalHistory, + ); + + this.router.delete( + `${this.path}/patient/medical-history/:recordId`, + /* + #swagger.path = '/medical-records/patient/medical-history/{recordId}' + #swagger.method = 'delete' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient soft-deletes one of their own MEDICAL_HISTORY records. Also removes it from blockchain and IPFS.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.parameters['recordId'] = { + in: 'path', + description: 'UUID of the record to delete', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Medical history entry deleted successfully', + schema: { message: 'Medical history entry deleted successfully' } + } + #swagger.responses[401] = { description: 'Unauthorized' } + #swagger.responses[404] = { description: 'Record not found or not owned by patient' } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.deletePatientMedicalHistory, + ); + + this.router.get( + `${this.path}/patient/medical-history`, + /* + #swagger.path = '/medical-records/patient/medical-history' + #swagger.method = 'get' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient retrieves all their MEDICAL_HISTORY records, decrypted and authorized across all clinics on-chain.' + + #swagger.parameters['Authorization'] = { + in: 'cookie', + description: 'Bearer token for authentication', + required: true, + type: 'string' + } + + #swagger.responses[200] = { + description: 'Medical history retrieved successfully', + schema: { + message: 'Medical history retrieved successfully', + data: [{ recordId: 'uuid-string', content: {} }] + } + } + #swagger.responses[401] = { + description: 'Unauthorized – missing or invalid token' + } + */ + AuthMiddleware, + RoleMiddleware(Role.PATIENT), + this.medicalRecordController.getPatientMedicalHistory, + ); + + this.router.get( + `${this.path}/:patientId/visit-summaries`, + /* + #swagger.path = '/medical-records/{patientId}/visit-summaries' + #swagger.method = 'get' + #swagger.tags = ['Medical Records - Doctor'] + #swagger.description = 'Doctor retrieves VISIT_SUMMARY records for a patient. Only returns records the doctor\'s clinic(s) are authorized to access on-chain.' + #swagger.parameters['patientId'] = { in: 'path', required: true, type: 'string', description: 'UUID of the patient' } + #swagger.responses[200] = { description: 'Visit summaries retrieved successfully', schema: { message: 'Visit summaries retrieved successfully', data: [{ recordId: 'uuid-string', content: {} }] } } + #swagger.responses[401] = { description: 'Unauthorized' } + #swagger.responses[403] = { description: 'Access denied' } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.medicalRecordController.getDoctorPatientVisitSummaries, + ); + + this.router.get( + `${this.path}/:patientId/medical-history`, + /* + #swagger.path = '/medical-records/{patientId}/medical-history' + #swagger.method = 'get' + #swagger.tags = ['Medical Records - Doctor'] + #swagger.description = 'Doctor retrieves MEDICAL_HISTORY records for a patient. Only returns records the doctor\'s clinic(s) are authorized to access on-chain.' + #swagger.parameters['patientId'] = { in: 'path', required: true, type: 'string', description: 'UUID of the patient' } + #swagger.responses[200] = { description: 'Medical history retrieved successfully', schema: { message: 'Medical history retrieved successfully', data: [{ recordId: 'uuid-string', content: {} }] } } + #swagger.responses[401] = { description: 'Unauthorized' } + #swagger.responses[403] = { description: 'Access denied' } + */ + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.medicalRecordController.getDoctorPatientMedicalHistory, ); this.router.post( `${this.path}/grant-access`, /* - #swagger.path = '/record/grant-access' - #swagger.method = 'post' - #swagger.tags = ['Medical Records'] - #swagger.description = 'Patient grants a target clinic access to ALL their medical records across all owner clinics.' + #swagger.path = '/medical-records/grant-access' + #swagger.method = 'post' + #swagger.tags = ['Medical Records - Patient'] + #swagger.description = 'Patient grants a target clinic access to ALL their medical records across all owner clinics.' #swagger.parameters['Authorization'] = { in: 'cookie', @@ -202,7 +345,7 @@ export class MedicalRecordRoute implements Routes { */ AuthMiddleware, RoleMiddleware(Role.PATIENT), - this.medicalRecordController.grantAccess, + this.medicalRecordController.grantPatientAccess, ); this.router.delete( diff --git a/src/services/identity-storage.service.ts b/src/services/identity-storage.service.ts index 4060fd3..edd1cae 100644 --- a/src/services/identity-storage.service.ts +++ b/src/services/identity-storage.service.ts @@ -4,9 +4,10 @@ import * as crypto from 'crypto'; import { FabricIdentity, FabricIdentityInput } from '@/interfaces/fabric-identity.interface'; import { HttpException } from '@/exceptions/HttpException'; -class IdentityStorageService { +export class IdentityStorageService { private readonly storagePath: string; private readonly encryptionKey: Buffer; + private readonly defaultClinicId = 'default-clinic'; constructor() { this.storagePath = process.env.FABRIC_IDENTITY_STORAGE_PATH || @@ -96,6 +97,14 @@ class IdentityStorageService { updatedAt: identity.updatedAt, })); } + public async defaultIdentity(): Promise { + const identities = await this.readAll(); + const defaultIdentity = identities.find(id => id.clinicId === this.defaultClinicId); + if (!defaultIdentity) { + throw new HttpException(404, 'Default identity not found'); + } + return defaultIdentity; + } public async deleteIdentity(clinicId: string): Promise { const identities = await this.readAll(); diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 3714d92..7a69754 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -10,411 +10,527 @@ import { KeyManagementService } from '@/services/key-management.service'; import { createBilingualError, ErrorMessages } from '@/utils/errorMessages'; import { randomUUID } from 'crypto'; import FabricService from '@/services/fabric.service'; - +import { RecordType } from '@/interfaces/enums.interface'; +import { IdentityStorageService } from '@/services/identity-storage.service'; @Service() export class MedicalRecordService { + private ipfsService = new IpfsService(); + private encryptionService = new EncryptionService(); + private keyManagementService = new KeyManagementService(); + private fabricService = new FabricService(); + private identityStorageService = new IdentityStorageService(); + + public async createMedicalRecord( + clinicId: string, + patientId: string, + doctorId: string, + fileData: CreateMedicalRecordDto, + fileBuffer: Buffer, + fileName: string, + mimeType: string, + ): Promise { + const recordId = randomUUID(); + + const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(fileBuffer, recordDEK); + recordDEK.fill(0); + + const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); + + await prisma.medicalRecord.create({ + data: { + id: recordId, + patient_id: patientId, + doctor_id: doctorId, + clinic_id: clinicId, + appointment_id: (fileData as any).appointmentId, + name: fileData.name, + cid: cid, + type: fileData.type, + mime_type: mimeType, + } as Prisma.MedicalRecordUncheckedCreateInput, + }); + + await this.fabricService.addRecord(clinicId, { + patientId, + recordId, + doctorId, + type: fileData.type, + ipfsCidKey: cid, + }); + } + + public async getRecordFile(callerClinicId: string, recordId: string): Promise { + const record = await prisma.medicalRecord.findFirst({ + where: { + id: recordId, + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, + }, + }); + + if (!record) { + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } - private ipfsService = new IpfsService(); - private encryptionService = new EncryptionService(); - private keyManagementService = new KeyManagementService(); - private fabricService = new FabricService(); - - public async createMedicalRecord( - clinicId: string, - patientId: string, - doctorId: string, - fileData: CreateMedicalRecordDto, - fileBuffer: Buffer, - fileName: string, - mimeType: string, - ): Promise { - const recordId = randomUUID(); - - const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); - const encryptedFile = this.encryptionService.encryptFile(fileBuffer, recordDEK); - recordDEK.fill(0); + const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, record.patient_id); + const isAuthorized = authorizedRecords.some(r => r.recordId === recordId); + if (!isAuthorized) { + throw new HttpException(403, 'Access denied: your clinic is not authorized to access this record'); + } - const cid = await this.ipfsService.uploadFile(encryptedFile, fileName, mimeType); - - await prisma.medicalRecord.create({ - data: { - id: recordId, - patient_id: patientId, - doctor_id: doctorId, - clinic_id: clinicId, - appointment_id: (fileData as any).appointmentId, - name: fileData.name, - cid: cid, - type: fileData.type, - mime_type: mimeType, - } as Prisma.MedicalRecordUncheckedCreateInput, - }); - - // Sync to blockchain ledger - await this.fabricService.addRecord(clinicId, { - patientId, - recordId, - doctorId, - type: fileData.type, - ipfsCidKey: cid, - }); + const encryptedFile = await this.ipfsService.getFile(record.cid); + + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); + + return { + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, + name: record.name, + type: record.type, + mime_type: record.mime_type, + cid: record.cid, + buffer: decryptedFile, + }; + } + + public async checkIpfsHealth(): Promise<{ status: string; message: string }> { + return this.ipfsService.checkHealth(); + } + + public async getPatientFiles(patientId: string): Promise { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, + }, + orderBy: { + created_at: 'desc', + }, + }); + + return records.map(record => ({ + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, + name: record.name, + type: record.type, + cid: record.cid, + mime_type: record.mime_type, + })); + } + + public async deleteRecord(callerClinicId: string, recordId: string): Promise { + const record = await prisma.medicalRecord.findFirst({ + where: { + id: recordId, + deleted_at: null, + }, + }); + + if (!record) { + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); } - public async getRecordFile(callerClinicId: string, recordId: string): Promise { - const record = await prisma.medicalRecord.findFirst({ - where: { - id: recordId, - deleted_at: null - }, - select: { - id: true, - patient_id: true, - clinic_id: true, - doctor_id: true, - appointment_id: true, - name: true, - type: true, - mime_type: true, - cid: true, - } - }); - - if (!record) { - const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); - throw new HttpException(error.status, error.message, error.messageAr); - } - - // Verify the caller's clinic is authorized on-chain. - // GetRecordsByPatient enforces MSP authorization — if the caller is not the owner - // or not in authorizedMsps, the record won't appear in the result. - const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, record.patient_id); - const isAuthorized = authorizedRecords.some(r => r.recordId === recordId); - if (!isAuthorized) { - throw new HttpException(403, 'Access denied: your clinic is not authorized to access this record'); - } - const encryptedFile = await this.ipfsService.getFile(record.cid); + if (record.deleted_at) { + const error = createBilingualError(404, ErrorMessages.RECORD_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + await this.fabricService.deleteRecord(callerClinicId, record.patient_id, recordId); + + await prisma.medicalRecord.update({ + where: { id: recordId }, + data: { deleted_at: new Date() }, + }); + } - // Decrypt using the owner clinic's key (the clinic that created the record) + public async grantAccess(patientId: string, targetClinicId: string): Promise { + const records = await prisma.medicalRecord.findMany({ + where: { patient_id: patientId, deleted_at: null }, + select: { clinic_id: true }, + }); + + const ownerClinicIds = [...new Set(records.map(r => r.clinic_id))]; + + for (const ownerClinicId of ownerClinicIds) { + await this.fabricService.grantAccess(ownerClinicId, patientId, targetClinicId); + } + } + + public async addDoctorRecord(clinicId: string, patientId: string, doctorId: string, dto: CreateDoctorRecordJsonDto): Promise { + const clinicDoctor = await prisma.clinicDoctor.findUnique({ + where: { clinic_id_doctor_id: { clinic_id: clinicId, doctor_id: doctorId } }, + }); + if (!clinicDoctor) { + throw new HttpException(403, 'Doctor is not associated with this clinic'); + } + + const recordId = randomUUID(); + + const contentBuffer = Buffer.from(JSON.stringify(dto.content), 'utf-8'); + const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(contentBuffer, recordDEK); + recordDEK.fill(0); + + const cid = await this.ipfsService.uploadFile(encryptedFile, `${recordId}.enc`, 'application/octet-stream'); + + await prisma.medicalRecord.create({ + data: { + id: recordId, + patient_id: patientId, + doctor_id: doctorId, + clinic_id: clinicId, + name: dto.name, + cid: cid, + type: dto.type, + mime_type: 'application/json', + } as Prisma.MedicalRecordUncheckedCreateInput, + }); + + await this.fabricService.addRecord(clinicId, { + patientId, + recordId, + doctorId, + type: dto.type, + ipfsCidKey: cid, + }); + + return recordId; + } + + public async getPatientRecordsForDoctor(callerClinicId: string, patientId: string): Promise { + const authorizedOnChain = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); + const authorizedIds = new Set(authorizedOnChain.map(r => r.recordId)); + + const records = await prisma.medicalRecord.findMany({ + where: { patient_id: patientId, deleted_at: null }, + select: { + id: true, + patient_id: true, + clinic_id: true, + doctor_id: true, + appointment_id: true, + name: true, + type: true, + mime_type: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + return records + .filter(r => authorizedIds.has(r.id)) + .map(record => ({ + id: record.id, + patient_id: record.patient_id, + clinic_id: record.clinic_id, + doctor_id: record.doctor_id ?? undefined, + appointment_id: record.appointment_id ?? undefined, + name: record.name, + type: record.type, + cid: record.cid, + mime_type: record.mime_type, + })); + } + + public async getSOAPNotes(callerClinicId: string, patientId: string): Promise> { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + mime_type: 'application/json', + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); + const authorizedIds = new Set(authorizedRecords.map(r => r.recordId)); + + const results: Array<{ recordId: string; content: any }> = []; + + for (const record of records) { + if (!authorizedIds.has(record.id)) continue; + + try { + const encryptedFile = await this.ipfsService.getFile(record.cid); const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); recordDEK.fill(0); - return { - id: record.id, - patient_id: record.patient_id, - clinic_id: record.clinic_id, - doctor_id: record.doctor_id ?? undefined, - appointment_id: record.appointment_id ?? undefined, - name: record.name, - type: record.type, - mime_type: record.mime_type, - cid: record.cid, - buffer: decryptedFile, - }; + const content = JSON.parse(decryptedFile.toString('utf-8')); + results.push({ recordId: record.id, content }); + } catch (e) { + console.warn(`Skipping record ${record.id}: not a JSON record (${e.message})`); + } } - public async checkIpfsHealth(): Promise<{ status: string; message: string }> { - return this.ipfsService.checkHealth(); - } - public async getPatientFiles(patientId: string): Promise { - const records = await prisma.medicalRecord.findMany({ - where: { - patient_id: patientId, - deleted_at: null - }, - select: { - id: true, - patient_id: true, - clinic_id: true, - doctor_id: true, - appointment_id: true, - name: true, - type: true, - mime_type: true, - cid: true, - }, - orderBy: { - created_at: 'desc' - }, - }); - - - - return records.map(record => ({ - id: record.id, - patient_id: record.patient_id, - clinic_id: record.clinic_id, - doctor_id: record.doctor_id ?? undefined, - appointment_id: record.appointment_id ?? undefined, - name: record.name, - type: record.type, - cid: record.cid, - mime_type: record.mime_type, - })); - } - - public async deleteRecord(callerClinicId: string, recordId: string): Promise { - const record = await prisma.medicalRecord.findFirst({ - where: { - id: recordId, - deleted_at: null - }, - }); - - if (!record) { - const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); - throw new HttpException(error.status, error.message, error.messageAr); - } - - if (record.deleted_at) { - const error = createBilingualError(404, ErrorMessages.RECORD_ALREADY_DELETED); - throw new HttpException(error.status, error.message, error.messageAr); - } - - // Chaincode enforces ownerMSP check — non-owners get a chaincode error - await this.fabricService.deleteRecord(callerClinicId, record.patient_id, recordId); - - // Soft-delete in DB - await prisma.medicalRecord.update({ - where: { id: recordId }, - data: { deleted_at: new Date() }, - }); + return results; + } + + public async getSOAPNotesForPatient(patientId: string, type?: RecordType): Promise> { + const records = await prisma.medicalRecord.findMany({ + where: { + patient_id: patientId, + mime_type: 'application/json', + ...(type ? { type } : {}), + deleted_at: null, + }, + select: { + id: true, + patient_id: true, + clinic_id: true, + cid: true, + }, + orderBy: { created_at: 'desc' }, + }); + + const distinctClinicIds = [...new Set(records.map(r => r.clinic_id))]; + + const authorizedIds = new Set(); + for (const clinicId of distinctClinicIds) { + const authorizedRecords = await this.fabricService.getRecordsByPatient(clinicId, patientId); + authorizedRecords.forEach(r => authorizedIds.add(r.recordId)); } + const results: Array<{ recordId: string; content: any }> = []; - public async grantAccess(patientId: string, targetClinicId: string): Promise { - // Fetch all records for this patient to find distinct owner clinics - const records = await prisma.medicalRecord.findMany({ - where: { patient_id: patientId, deleted_at: null }, - select: { clinic_id: true }, - }); + for (const record of records) { + if (!authorizedIds.has(record.id)) continue; - // Group by owner clinic — each clinic MSP must grant independently - const ownerClinicIds = [...new Set(records.map(r => r.clinic_id))]; + try { + const encryptedFile = await this.ipfsService.getFile(record.cid); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); - for (const ownerClinicId of ownerClinicIds) { - await this.fabricService.grantAccess(ownerClinicId, patientId, targetClinicId); - } + const content = JSON.parse(decryptedFile.toString('utf-8')); + results.push({ recordId: record.id, content }); + } catch (e) { + console.warn(`Skipping record ${record.id}: not a JSON record (${e.message})`); + } } - /** - * Doctor-initiated record creation (JSON-based). - * Validates the doctor works in the clinic, serialises the JSON content to a Buffer, - * encrypts it, uploads to IPFS, stores in DB, syncs to blockchain, and returns the record ID. - */ - public async addDoctorRecord( - clinicId: string, - patientId: string, - doctorId: string, - dto: CreateDoctorRecordJsonDto, - ): Promise { - // Validate the doctor is associated with this clinic - const clinicDoctor = await prisma.clinicDoctor.findUnique({ - where: { clinic_id_doctor_id: { clinic_id: clinicId, doctor_id: doctorId } }, - }); - if (!clinicDoctor) { - throw new HttpException(403, 'Doctor is not associated with this clinic'); - } - - const recordId = randomUUID(); - - // Serialise JSON content to a UTF-8 Buffer and encrypt it - const contentBuffer = Buffer.from(JSON.stringify(dto.content), 'utf-8'); - const recordDEK = await this.keyManagementService.getRecordDEK(clinicId, patientId, recordId); - const encryptedFile = this.encryptionService.encryptFile(contentBuffer, recordDEK); - recordDEK.fill(0); + return results; + } + + public async getMedicalHistory(patientId: string): Promise> { + return this.getSOAPNotesForPatient(patientId, RecordType.MEDICAL_HISTORY); + } + + public async addPatientMedicalHistory(patientId: string, dto: { name: string; content: Record }): Promise { + const recordId = randomUUID(); + + const contentBuffer = Buffer.from(JSON.stringify(dto.content), 'utf-8'); + const defaultClinicId = (await this.identityStorageService.defaultIdentity()).clinicId; + const recordDEK = await this.keyManagementService.getRecordDEK(defaultClinicId, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(contentBuffer, recordDEK); + recordDEK.fill(0); + + const cid = await this.ipfsService.uploadFile(encryptedFile, `${recordId}.enc`, 'application/octet-stream'); + + await prisma.medicalRecord.create({ + data: { + id: recordId, + patient_id: patientId, + clinic_id: defaultClinicId, + name: dto.name, + cid, + type: RecordType.MEDICAL_HISTORY, + mime_type: 'application/json', + } as Prisma.MedicalRecordUncheckedCreateInput, + }); + + await this.fabricService.addRecord(defaultClinicId, { + patientId, + recordId, + doctorId: patientId, + type: RecordType.MEDICAL_HISTORY, + ipfsCidKey: cid, + }); + + return recordId; + } + + public async updatePatientMedicalHistory( + patientId: string, + recordId: string, + dto: { name?: string; content?: Record }, + ): Promise { + const record = await prisma.medicalRecord.findFirst({ + where: { + id: recordId, + patient_id: patientId, + type: RecordType.MEDICAL_HISTORY, + deleted_at: null, + }, + }); + + if (!record) { + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } - // Upload as octet-stream — the file is encrypted binary regardless of original content type. - // Uploading as application/json causes Pinata's gateway to call .json() on the encrypted - // bytes when fetching, which throws a parse error. - const cid = await this.ipfsService.uploadFile(encryptedFile, `${recordId}.enc`, 'application/octet-stream'); - - await prisma.medicalRecord.create({ - data: { - id: recordId, - patient_id: patientId, - doctor_id: doctorId, - clinic_id: clinicId, - name: dto.name, - cid: cid, - type: dto.type, - mime_type: 'application/json', // logical type of the decrypted content - } as Prisma.MedicalRecordUncheckedCreateInput, - }); - - // Sync to blockchain ledger - await this.fabricService.addRecord(clinicId, { - patientId, - recordId, - doctorId, - type: dto.type, - ipfsCidKey: cid, - }); - - return recordId; + const oldCid = record.cid; + + if (dto.content !== undefined) { + const contentBuffer = Buffer.from(JSON.stringify(dto.content), 'utf-8'); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, patientId, recordId); + const encryptedFile = this.encryptionService.encryptFile(contentBuffer, recordDEK); + recordDEK.fill(0); + + const newCid = await this.ipfsService.uploadFile(encryptedFile, `${recordId}.enc`, 'application/octet-stream'); + + await prisma.medicalRecord.update({ + where: { id: recordId }, + data: { + cid: newCid, + ...(dto.name !== undefined ? { name: dto.name } : {}), + }, + }); + + try { + await this.ipfsService.deleteFile(oldCid); + } catch (e) { + console.warn(`Old IPFS file cleanup skipped for ${recordId}: ${e.message}`); + } + } else if (dto.name !== undefined) { + await prisma.medicalRecord.update({ + where: { id: recordId }, + data: { name: dto.name }, + }); + } + } + + public async deletePatientMedicalHistory(patientId: string, recordId: string): Promise { + const record = await prisma.medicalRecord.findFirst({ + where: { + id: recordId, + patient_id: patientId, + type: RecordType.MEDICAL_HISTORY, + deleted_at: null, + }, + }); + + if (!record) { + const error = createBilingualError(404, ErrorMessages.RECORD_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); } - /** - * Retrieves all SOAP_NOTE records for a patient, decrypts the JSON files, - * and returns their parsed contents as a list of objects. - */ - /** - * Doctor-facing: returns DB metadata for all of a patient's records - * that the caller's clinic is authorized to access on-chain. - */ - public async getPatientRecordsForDoctor(callerClinicId: string, patientId: string): Promise { - // Get on-chain authorized record IDs for this clinic - const authorizedOnChain = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); - const authorizedIds = new Set(authorizedOnChain.map(r => r.recordId)); - - const records = await prisma.medicalRecord.findMany({ - where: { patient_id: patientId, deleted_at: null }, - select: { - id: true, - patient_id: true, - clinic_id: true, - doctor_id: true, - appointment_id: true, - name: true, - type: true, - mime_type: true, - cid: true, - }, - orderBy: { created_at: 'desc' }, - }); - - return records - .filter(r => authorizedIds.has(r.id)) - .map(record => ({ - id: record.id, - patient_id: record.patient_id, - clinic_id: record.clinic_id, - doctor_id: record.doctor_id ?? undefined, - appointment_id: record.appointment_id ?? undefined, - name: record.name, - type: record.type, - cid: record.cid, - mime_type: record.mime_type, - })); + const defaultClinicId = (await this.identityStorageService.defaultIdentity()).clinicId; + await this.fabricService.deleteRecord(defaultClinicId, patientId, recordId); + + await prisma.medicalRecord.update({ + where: { id: recordId }, + data: { deleted_at: new Date() }, + }); + } + + public async getVisitSummariesForDoctor(doctorId: string, patientId: string): Promise> { + return this.getJsonRecordsForDoctor(doctorId, patientId, RecordType.VISIT); + } + + public async getMedicalHistoryForDoctor(doctorId: string, patientId: string): Promise> { + return this.getJsonRecordsForDoctor(doctorId, patientId, RecordType.MEDICAL_HISTORY); + } + + private async getJsonRecordsForDoctor(doctorId: string, patientId: string, type: RecordType): Promise> { + const records = await prisma.medicalRecord.findMany({ + where: { patient_id: patientId, mime_type: 'application/json', type: type, deleted_at: null }, + select: { id: true, patient_id: true, clinic_id: true, cid: true }, + orderBy: { created_at: 'desc' }, + }); + + const doctorClinics = await prisma.clinicDoctor.findMany({ + where: { doctor_id: doctorId }, + select: { clinic_id: true }, + }); + const doctorClinicIds = doctorClinics.map(c => c.clinic_id); + + const authorizedIds = new Set(); + for (const clinicId of doctorClinicIds) { + try { + const authorized = await this.fabricService.getRecordsByPatient(clinicId, patientId); + authorized.forEach(r => authorizedIds.add(r.recordId)); + } catch (e) { + console.warn(`Chain check skipped for clinic ${clinicId}: ${e.message}`); + } } - public async getSOAPNotes(callerClinicId: string, patientId: string): Promise> { - const records = await prisma.medicalRecord.findMany({ - where: { - patient_id: patientId, - mime_type: 'application/json', - deleted_at: null, - }, - select: { - id: true, - patient_id: true, - clinic_id: true, - cid: true, - }, - orderBy: { created_at: 'desc' }, - }); - - // Verify on-chain access once for this patient - const authorizedRecords = await this.fabricService.getRecordsByPatient(callerClinicId, patientId); - const authorizedIds = new Set(authorizedRecords.map(r => r.recordId)); - - const results: Array<{ recordId: string; content: any }> = []; - - for (const record of records) { - if (!authorizedIds.has(record.id)) continue; - - try { - const encryptedFile = await this.ipfsService.getFile(record.cid); - const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); - const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); - recordDEK.fill(0); - - const content = JSON.parse(decryptedFile.toString('utf-8')); - results.push({ recordId: record.id, content }); - } catch (e) { - // Skip records that are binary files (not JSON-based) - console.warn(`⚠️ Skipping record ${record.id}: not a JSON record (${e.message})`); - } - } - - return results; + const results: Array<{ recordId: string; content: any }> = []; + for (const record of records) { + if (!authorizedIds.has(record.id)) continue; + try { + const encryptedFile = await this.ipfsService.getFile(record.cid); + const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); + const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); + recordDEK.fill(0); + results.push({ recordId: record.id, content: JSON.parse(decryptedFile.toString('utf-8')) }); + } catch (e) { + console.warn(`Skipping record ${record.id}: ${e.message}`); + } } - public async getSOAPNotesForPatient(patientId: string): Promise> { - const records = await prisma.medicalRecord.findMany({ - where: { - patient_id: patientId, - mime_type: 'application/json', - deleted_at: null, - }, - select: { - id: true, - patient_id: true, - clinic_id: true, - cid: true, - }, - orderBy: { created_at: 'desc' }, - }); - - // Get all distinct clinics from the records - const distinctClinicIds = [...new Set(records.map(r => r.clinic_id))]; - - // Call getRecordsByPatient for each clinic sequentially to avoid concurrent gRPC channel conflicts - const authorizedIds = new Set(); - for (const clinicId of distinctClinicIds) { - const authorizedRecords = await this.fabricService.getRecordsByPatient(clinicId, patientId); - authorizedRecords.forEach(r => authorizedIds.add(r.recordId)); - } - - const results: Array<{ recordId: string; content: any }> = []; - - for (const record of records) { - if (!authorizedIds.has(record.id)) continue; - - try { - const encryptedFile = await this.ipfsService.getFile(record.cid); - const recordDEK = await this.keyManagementService.getRecordDEK(record.clinic_id, record.patient_id, record.id); - const decryptedFile = this.encryptionService.decryptFile(encryptedFile, recordDEK); - recordDEK.fill(0); - - const content = JSON.parse(decryptedFile.toString('utf-8')); - results.push({ recordId: record.id, content }); - } catch (e) { - // Skip records that are binary files (not JSON-based) - console.warn(`⚠️ Skipping record ${record.id}: not a JSON record (${e.message})`); - } - } - - return results; + return results; + } + + public async deleteAllRecords(): Promise<{ deleted: number }> { + const records = await prisma.medicalRecord.findMany({ + select: { id: true, patient_id: true, clinic_id: true, cid: true }, + }); + + for (const record of records) { + try { + await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, record.id); + } catch (e) { + console.warn(`Chain delete skipped for ${record.id}: ${e.message}`); + } + + try { + await this.ipfsService.deleteFile(record.cid); + } catch (e) { + console.warn(`IPFS delete skipped for ${record.id}: ${e.message}`); + } + + await prisma.medicalRecord.delete({ where: { id: record.id } }); } - /** - * DEV ONLY — hard-deletes every medical record from DB, IPFS, and the blockchain. - */ - public async deleteAllRecords(): Promise<{ deleted: number }> { - const records = await prisma.medicalRecord.findMany({ - select: { id: true, patient_id: true, clinic_id: true, cid: true }, - }); - - for (const record of records) { - // 1. Remove from blockchain - try { - await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, record.id); - } catch (e) { - console.warn(`⚠️ Chain delete skipped for ${record.id}: ${e.message}`); - } - - // 2. Remove from IPFS - try { - await this.ipfsService.deleteFile(record.cid); - } catch (e) { - console.warn(`⚠️ IPFS delete skipped for ${record.id}: ${e.message}`); - } - - // 3. Hard-delete from DB - await prisma.medicalRecord.delete({ where: { id: record.id } }); - } - - return { deleted: records.length }; - } -} \ No newline at end of file + return { deleted: records.length }; + } +} diff --git a/src/swagger-output.json b/src/swagger-output.json index 63a4511..6a7ac05 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -8546,10 +8546,10 @@ } } }, - "/record/health/ipfs": { + "/medical-records/health/ipfs": { "get": { "tags": [ - "Medical Records" + "Medical Records - Public" ], "description": "Checks connectivity to the IPFS (Pinata) service", "responses": { @@ -8578,12 +8578,115 @@ } } }, - "/record/patient": { + "/medical-records/clinics/{clinicId}/patients/{patientId}/visit-summaries": { + "post": { + "tags": [ + "Medical Records - Doctor" + ], + "description": "Doctor creates a JSON-based medical record for a patient. Validates the doctor works at the clinic. Content is encrypted and stored on IPFS.", + "parameters": [ + { + "name": "clinicId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the clinic" + }, + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the patient" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Medical record payload", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "SOAP Note 2026-03-08" + }, + "type": { + "type": "string", + "example": "SOAP_NOTE" + }, + "content": { + "type": "object", + "properties": { + "subjective": { + "type": "string", + "example": "Patient reports headache" + }, + "objective": { + "type": "string", + "example": "BP 120/80" + }, + "assessment": { + "type": "string", + "example": "Tension headache" + }, + "plan": { + "type": "string", + "example": "Ibuprofen 400mg" + } + } + } + } + } + } + ], + "responses": { + "201": { + "description": "Medical record created successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical record created successfully" + }, + "data": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Validation failed" + }, + "403": { + "description": "Doctor is not associated with this clinic" + } + } + } + }, + "/medical-records/patient/visit-summaries": { "get": { "tags": [ - "Medical Records" + "Medical Records - Patient" ], - "description": "Retrieves all medical record metadata for the authenticated patient (no file bytes)", + "description": "Patient retrieves all their VISIT_SUMMARY records, decrypted and authorized across all clinics on-chain.", "parameters": [ { "name": "Authorization", @@ -8595,54 +8698,26 @@ ], "responses": { "200": { - "description": "Medical records retrieved successfully", + "description": "Visit summaries retrieved successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical records retrieved successfully" + "example": "Visit summaries retrieved successfully" }, "data": { "type": "array", "items": { "type": "object", "properties": { - "id": { - "type": "string", - "example": "uuid-string" - }, - "patient_id": { - "type": "string", - "example": "uuid-string" - }, - "clinic_id": { - "type": "string", - "example": "uuid-string" - }, - "doctor_id": { - "type": "string", - "example": "uuid-string" - }, - "appointment_id": { + "recordId": { "type": "string", "example": "uuid-string" }, - "name": { - "type": "string", - "example": "Blood Test Results" - }, - "type": { - "type": "string", - "example": "LAB_RESULT" - }, - "mime_type": { - "type": "string", - "example": "application/pdf" - }, - "cid": { - "type": "string", - "example": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + "content": { + "type": "object", + "properties": {} } } } @@ -8659,27 +8734,13 @@ } } }, - "/record/{clinicId}/{patientId}/soap-note": { + "/medical-records/patient/medical-history": { "post": { "tags": [ - "Medical Records" + "Medical Records - Patient" ], - "description": "Doctor creates a JSON-based medical record for a patient. Validates the doctor works at the clinic. Content is encrypted and stored on IPFS.", + "description": "Patient adds a new MEDICAL_HISTORY entry. Type is fixed — only name and content are required.", "parameters": [ - { - "name": "clinicId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the clinic" - }, - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string", - "description": "UUID of the patient" - }, { "name": "Authorization", "in": "cookie", @@ -8690,37 +8751,34 @@ { "name": "body", "in": "body", - "description": "Medical record payload", "required": true, "schema": { "type": "object", "properties": { "name": { "type": "string", - "example": "SOAP Note 2026-03-08" - }, - "type": { - "type": "string", - "example": "SOAP_NOTE" + "example": "Previous Surgeries" }, "content": { "type": "object", "properties": { - "subjective": { - "type": "string", - "example": "Patient reports headache" - }, - "objective": { - "type": "string", - "example": "BP 120/80" - }, - "assessment": { - "type": "string", - "example": "Tension headache" + "conditions": { + "type": "array", + "example": [ + "hypertension" + ], + "items": { + "type": "string" + } }, - "plan": { - "type": "string", - "example": "Ibuprofen 400mg" + "surgeries": { + "type": "array", + "example": [ + "appendectomy" + ], + "items": { + "type": "string" + } } } } @@ -8730,13 +8788,13 @@ ], "responses": { "201": { - "description": "Medical record created successfully", + "description": "Medical history entry created successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical record created successfully" + "example": "Medical history entry created successfully" }, "data": { "type": "object", @@ -8756,18 +8814,16 @@ "400": { "description": "Validation failed" }, - "403": { - "description": "Doctor is not associated with this clinic" + "401": { + "description": "Unauthorized" } } - } - }, - "/record/patient/soap-notes": { + }, "get": { "tags": [ - "Medical Records" + "Medical Records - Patient" ], - "description": "Patient retrieves all their own SOAP notes, authorized across all clinics on-chain.", + "description": "Patient retrieves all their MEDICAL_HISTORY records, decrypted and authorized across all clinics on-chain.", "parameters": [ { "name": "Authorization", @@ -8779,13 +8835,13 @@ ], "responses": { "200": { - "description": "SOAP notes retrieved successfully", + "description": "Medical history retrieved successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "SOAP notes retrieved successfully" + "example": "Medical history retrieved successfully" }, "data": { "type": "array", @@ -8815,25 +8871,104 @@ } } }, - "/record/{clinicId}/{recordId}": { - "delete": { + "/medical-records/patient/medical-history/{recordId}": { + "patch": { "tags": [ - "Medical Records" + "Medical Records - Patient" ], - "description": "Soft-deletes a medical record. The clinicId identifies the caller\\'s clinic; chaincode enforces owner-only deletion.", + "description": "Patient updates an existing MEDICAL_HISTORY record they own. At least one of name or content must be provided. If content changes, the file is re-encrypted and re-uploaded to IPFS.", "parameters": [ { - "name": "clinicId", + "name": "recordId", "in": "path", "required": true, "type": "string", - "description": "UUID of the caller's clinic (must be the record owner)" + "description": "UUID of the record to update" + }, + { + "name": "Authorization", + "in": "cookie", + "description": "Bearer token for authentication", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Updated History Title" + }, + "content": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "example": [ + "hypertension" + ], + "items": { + "type": "string" + } + }, + "surgeries": { + "type": "array", + "example": [ + "appendectomy" + ], + "items": { + "type": "string" + } + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Medical history entry updated successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical history entry updated successfully" + } + }, + "xml": { + "name": "main" + } + } + }, + "400": { + "description": "Validation failed" }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Record not found or not owned by patient" + } + } + }, + "delete": { + "tags": [ + "Medical Records - Patient" + ], + "description": "Patient soft-deletes one of their own MEDICAL_HISTORY records. Also removes it from blockchain and IPFS.", + "parameters": [ { "name": "recordId", "in": "path", "required": true, - "type": "string" + "type": "string", + "description": "UUID of the record to delete" }, { "name": "Authorization", @@ -8841,24 +8976,73 @@ "description": "Bearer token for authentication", "required": true, "type": "string" + } + ], + "responses": { + "200": { + "description": "Medical history entry deleted successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical history entry deleted successfully" + } + }, + "xml": { + "name": "main" + } + } }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Record not found or not owned by patient" + } + } + } + }, + "/medical-records/{patientId}/visit-summaries": { + "get": { + "tags": [ + "Medical Records - Doctor" + ], + "description": "Doctor retrieves VISIT_SUMMARY records for a patient. Only returns records the doctor\\'s clinic(s) are authorized to access on-chain.", + "parameters": [ { - "name": "id", + "name": "patientId", "in": "path", - "description": "UUID of the medical record to delete", "required": true, - "type": "string" + "type": "string", + "description": "UUID of the patient" } ], "responses": { "200": { - "description": "Medical record deleted successfully", + "description": "Visit summaries retrieved successfully", "schema": { "type": "object", "properties": { "message": { "type": "string", - "example": "Medical record deleted successfully" + "example": "Visit summaries retrieved successfully" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + }, + "content": { + "type": "object", + "properties": {} + } + } + } } }, "xml": { @@ -8867,21 +9051,74 @@ } }, "401": { - "description": "Unauthorized – missing or invalid token" + "description": "Unauthorized" }, "403": { - "description": "Only the owner clinic can delete this record" + "description": "Access denied" + } + } + } + }, + "/medical-records/{patientId}/medical-history": { + "get": { + "tags": [ + "Medical Records - Doctor" + ], + "description": "Doctor retrieves MEDICAL_HISTORY records for a patient. Only returns records the doctor\\'s clinic(s) are authorized to access on-chain.", + "parameters": [ + { + "name": "patientId", + "in": "path", + "required": true, + "type": "string", + "description": "UUID of the patient" + } + ], + "responses": { + "200": { + "description": "Medical history retrieved successfully", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Medical history retrieved successfully" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "example": "uuid-string" + }, + "content": { + "type": "object", + "properties": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } }, - "404": { - "description": "Record not found or already deleted" + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Access denied" } } } }, - "/record/grant-access": { + "/medical-records/grant-access": { "post": { "tags": [ - "Medical Records" + "Medical Records - Patient" ], "description": "Patient grants a target clinic access to ALL their medical records across all owner clinics.", "parameters": [ From 593a31e3b763db9c1656e63edefaf3c62e535579 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 13 Mar 2026 04:26:01 +0200 Subject: [PATCH 197/210] feat: implement backup service for medical records management --- .gitignore | 4 +- src/controllers/fabric.controller.ts | 60 ------- src/interfaces/medical-records.interface.ts | 1 + src/routes/fabric.route.ts | 78 -------- src/services/backup.service.ts | 164 +++++++++++++++++ src/services/fabric.service.ts | 182 +++++++++++++------ src/services/medical-records.service.ts | 10 ++ src/swagger-output.json | 189 -------------------- 8 files changed, 306 insertions(+), 382 deletions(-) create mode 100644 src/services/backup.service.ts diff --git a/.gitignore b/.gitignore index 2efe8c4..26b2059 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,6 @@ fabric-identities.json # Temporary folders docker-compose-local.yml docs -uploads \ No newline at end of file +uploads +data/backup_keys.json +data/backup_records.json diff --git a/src/controllers/fabric.controller.ts b/src/controllers/fabric.controller.ts index 67f5246..67d077d 100644 --- a/src/controllers/fabric.controller.ts +++ b/src/controllers/fabric.controller.ts @@ -39,7 +39,6 @@ class FabricController { } }; - public deleteIdentity = async (req: Request, res: Response, next: NextFunction): Promise => { try { const clinicId = req.params.clinicId; @@ -60,65 +59,6 @@ class FabricController { } }; - public getAllRecords = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const clinicId = req.params.clinicId; - const records = await this.fabricService.getAllRecords(clinicId); - res.status(200).json({ data: records, message: 'findAll' }); - } catch (error) { - next(error); - } - }; - - public getRecordsByPatient = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const clinicId = req.params.clinicId; - const patientId = req.params.patientId; - const records = await this.fabricService.getRecordsByPatient(clinicId, patientId); - res.status(200).json({ data: records, message: 'findAll' }); - } catch (error) { - next(error); - } - }; - - public addRecord = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const clinicId = req.params.clinicId; - await this.fabricService.addRecord(clinicId, req.body); - res.status(201).json({ message: 'created' }); - } catch (error) { - next(error); - } - }; - - public updateRecord = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const clinicId = req.params.clinicId; - const patientId = req.params.patientId; - await this.fabricService.updateRecord(clinicId, patientId, req.body); - res.status(200).json({ message: 'updated' }); - } catch (error) { - next(error); - } - }; - - public grantAccess = async (req: Request, res: Response, next: NextFunction): Promise => { - try { - const clinicId = req.params.clinicId; - const patientId = req.params.patientId; - const { targetMsp } = req.body; - - if (!targetMsp) { - throw new HttpException(400, 'targetMsp is required'); - } - - await this.fabricService.grantAccess(clinicId, patientId, targetMsp); - res.status(200).json({ message: 'Access granted successfully' }); - } catch (error) { - next(error); - } - }; - public initLedger = async (req: Request, res: Response, next: NextFunction): Promise => { try { const clinicId = req.params.clinicId; diff --git a/src/interfaces/medical-records.interface.ts b/src/interfaces/medical-records.interface.ts index 542a54c..e76a757 100644 --- a/src/interfaces/medical-records.interface.ts +++ b/src/interfaces/medical-records.interface.ts @@ -6,4 +6,5 @@ export interface MedicalRecord { ipfsCidKey: string; ownerMsp?: string; authorizedMsps?: string[]; + deleted?: boolean; } diff --git a/src/routes/fabric.route.ts b/src/routes/fabric.route.ts index d6d602b..e7cdc31 100644 --- a/src/routes/fabric.route.ts +++ b/src/routes/fabric.route.ts @@ -80,89 +80,11 @@ export class FabricRoute implements Routes { */ this.fabricController.initLedger, ); - - // Medical Records Routes (require X-Fabric-Identity header) - this.router.get( - '/records', - /* - #swagger.tags = ['MedicalRecords'] - #swagger.parameters['X-Fabric-Identity'] = { - in: 'header', - description: 'Identity label (e.g., org1)', - required: true, - type: 'string' - } - */ - this.fabricController.getAllRecords, - ); - // Place the explicit health route before the dynamic `:patientId` route so the literal - // path `/records/health` is matched first instead of being captured as `:patientId = 'health'. this.router.get( '/records/health', /* #swagger.tags = ['MedicalRecords'] */ this.fabricController.checkHealth, ); - this.router.get( - '/records/:patientId', - /* - #swagger.tags = ['MedicalRecords'] - #swagger.description = 'Get all records for a patient (authorized MSPs only)' - */ - this.fabricController.getRecordsByPatient, - ); - this.router.post( - '/records', - /* - #swagger.tags = ['MedicalRecords'] - #swagger.description = 'Add a new medical record for a patient' - #swagger.parameters['body'] = { - in: 'body', - required: true, - schema: { - $patientId: 'patient-uuid', - $recordId: 'record-uuid', - $doctorId: 'doctor-uuid', - $type: 'LAB_RESULT', - $ipfsCidKey: 'bafybeigdyrzt...' - } - } - */ - ValidationMiddleware(CreateMedicalRecordDto), - this.fabricController.addRecord, - ); - this.router.put( - '/records/:patientId/:recordId', - /* - #swagger.tags = ['MedicalRecords'] - #swagger.description = 'Update an existing medical record (doctorId, type, optional new ipfsCidKey via transient)' - #swagger.parameters['body'] = { - in: 'body', - required: true, - schema: { - $recordId: 'uuid-record-id', - $doctorId: 'doctor-uuid', - $type: 'LAB_RESULT', - ipfsCidKey: 'optional-new-cid-key' - } - } - */ - ValidationMiddleware(UpdateMedicalRecordDto), - this.fabricController.updateRecord, - ); - - this.router.post( - '/records/:patientId/access', - /* - #swagger.tags = ['MedicalRecords'] - #swagger.description = 'Grant access to all records of a patient for a target MSP' - #swagger.parameters['body'] = { - in: 'body', - required: true, - schema: { $targetMsp: 'Org2MSP' } - } - */ - this.fabricController.grantAccess, - ); } } diff --git a/src/services/backup.service.ts b/src/services/backup.service.ts new file mode 100644 index 0000000..88c7614 --- /dev/null +++ b/src/services/backup.service.ts @@ -0,0 +1,164 @@ +import fs from 'fs'; +import path from 'path'; +import { MedicalRecord } from '@/interfaces/medical-records.interface'; + +export class BackupService { + private backupFilePath: string; + private keysFilePath: string; + + constructor() { + const dataDir = path.join(__dirname, '../../data'); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + this.backupFilePath = path.join(dataDir, 'backup_records.json'); + this.keysFilePath = path.join(dataDir, 'backup_keys.json'); + if (!fs.existsSync(this.backupFilePath)) { + fs.writeFileSync(this.backupFilePath, JSON.stringify([])); + } + if (!fs.existsSync(this.keysFilePath)) { + fs.writeFileSync(this.keysFilePath, JSON.stringify({})); + } + } + + private readBackup(): MedicalRecord[] { + try { + const data = fs.readFileSync(this.backupFilePath, 'utf8'); + const records: MedicalRecord[] = JSON.parse(data).filter((r: MedicalRecord) => !r.deleted); + return records; + } catch (error) { + console.error('Error reading backup file:', error); + return []; + } + } + + private writeBackup(records: MedicalRecord[]): void { + try { + fs.writeFileSync(this.backupFilePath, JSON.stringify(records, null, 2)); + } catch (error) { + console.error('Error writing to backup file:', error); + } + } + + private readKeys(): Record { + try { + const data = fs.readFileSync(this.keysFilePath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading keys file:', error); + return {}; + } + } + + private writeKeys(keys: Record): void { + try { + fs.writeFileSync(this.keysFilePath, JSON.stringify(keys, null, 2)); + } catch (error) { + console.error('Error writing to keys file:', error); + } + } + + public addRecord(record: MedicalRecord, ownerMsp: string): void { + const records = this.readBackup(); + const existingIndex = records.findIndex(r => r.recordId === record.recordId && r.patientId === record.patientId); + if (existingIndex === -1) { + records.push({ + ...record, + ownerMsp, + authorizedMsps: record.authorizedMsps || [], + }); + this.writeBackup(records); + } + } + + public getRecordsByPatient(patientId: string, clientMspId: string): MedicalRecord[] { + const records = this.readBackup(); + return records.filter(r => { + if (r.patientId !== patientId) return false; + if (r.deleted) { + return false; + } + if (clientMspId === 'admin' || r.ownerMsp === clientMspId) { + return true; + } + + // Check authorized list + if (r.authorizedMsps && r.authorizedMsps.includes(clientMspId)) { + return true; + } + + return false; + }); + } + + public getAllRecords(): MedicalRecord[] { + return this.readBackup(); + } + + public updateRecord(patientId: string, payload: Omit): void { + const records = this.readBackup(); + const index = records.findIndex(r => r.recordId === payload.recordId && r.patientId === patientId); + if (index !== -1) { + records[index] = { ...records[index], ...payload }; + this.writeBackup(records); + } + } + public deleteRecord(patientId: string, recordId: string): void { + const records = this.readBackup(); + const index = records.findIndex(r => r.recordId === recordId && r.patientId === patientId); + if (index !== -1) { + records[index].deleted = true; + this.writeBackup(records); + } + } + + public grantAccess(patientId: string, clientMspId: string, targetMsp: string): void { + const records = this.readBackup(); + let updated = false; + for (const record of records) { + if (record.patientId === patientId && record.ownerMsp === clientMspId) { + if (!record.authorizedMsps) { + record.authorizedMsps = []; + } + if (!record.authorizedMsps.includes(targetMsp)) { + record.authorizedMsps.push(targetMsp); + updated = true; + } + } + } + if (updated) { + this.writeBackup(records); + } + } + + public storeRecordKey(patientId: string, recordId: string, encryptedDEK: string): void { + const keys = this.readKeys(); + keys[`${patientId}:${recordId}`] = encryptedDEK; + this.writeKeys(keys); + } + + public getRecordKey(patientId: string, recordId: string): string { + const keys = this.readKeys(); + const key = keys[`${patientId}:${recordId}`]; + if (!key) { + throw new Error(`DEK not found in backup for patient ${patientId} and record ${recordId}`); + } + return key; + } + + public recordKeyExists(patientId: string, recordId: string): boolean { + const keys = this.readKeys(); + return !!keys[`${patientId}:${recordId}`]; + } + + public deleteAllRecords(): void { + try { + this.writeBackup([]); + this.writeKeys({}); + } catch (error) { + console.error('Error clearing backup file:', error); + } + } +} + +export const backupService = new BackupService(); diff --git a/src/services/fabric.service.ts b/src/services/fabric.service.ts index 37eff74..3f59aeb 100644 --- a/src/services/fabric.service.ts +++ b/src/services/fabric.service.ts @@ -6,6 +6,7 @@ import { HttpException } from '@/exceptions/HttpException'; import { MedicalRecord } from '@/interfaces/medical-records.interface'; import { FabricIdentity } from '@/interfaces/fabric-identity.interface'; import identityStorage from '@/services/identity-storage.service'; +import { backupService } from '@/services/backup.service'; interface GatewayConnection { gateway: Gateway; @@ -48,7 +49,7 @@ class FabricService { const connection = await this.createConnection(identity); this.connections.set(clinicId, connection); - console.log(`✅ Created new gateway connection for clinic: ${clinicId}`); + console.log(`Created new gateway connection for clinic: ${clinicId}`); return connection; } @@ -73,13 +74,12 @@ class FabricService { lastUsed: new Date(), }; } catch (error: any) { - console.error(`❌ Failed to create connection for clinic ${identity.clinicId}:`, error.message); + console.error(`Failed to create connection for clinic ${identity.clinicId}:`, error.message); throw new HttpException(503, `Failed to connect to Fabric network: ${error.message}`); } } private async newGrpcConnection(identity: FabricIdentity): Promise { - // gRPC requires the PEM to end with a newline — ensure it regardless of how it was stored const tlsPem = identity.tlsCertificate.endsWith('\n') ? identity.tlsCertificate : identity.tlsCertificate + '\n'; const tlsRootCert = Buffer.from(tlsPem); const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); @@ -114,99 +114,173 @@ class FabricService { } public async storeRecordKey(clinicId: string, patientId: string, recordId: string, encryptedDEK: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Submit Transaction: StoreRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); - await contract.submit('StoreRecordKey', { - arguments: [patientId, recordId], - transientData: { encryptedDEK: Buffer.from(encryptedDEK) }, - }); + backupService.storeRecordKey(patientId, recordId, encryptedDEK); + + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: StoreRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + await contract.submit('StoreRecordKey', { + arguments: [patientId, recordId], + transientData: { encryptedDEK: Buffer.from(encryptedDEK) }, + }); + } catch (error) { + console.error(`StoreRecordKey transaction failed for clinic ${clinicId}:`, error); + } } public async getRecordKey(clinicId: string, patientId: string, recordId: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Evaluate Transaction: GetRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); - const resultBytes = await contract.evaluateTransaction('GetRecordKey', patientId, recordId); - return this.utf8Decoder.decode(resultBytes); + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetRecordKey (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + const resultBytes = await contract.evaluateTransaction('GetRecordKey', patientId, recordId); + return this.utf8Decoder.decode(resultBytes); + } catch (error) { + console.error(`GetRecordKey error for clinic ${clinicId}, falling back to BackupService:`, error); + return backupService.getRecordKey(patientId, recordId); + } } public async recordKeyExists(clinicId: string, patientId: string, recordId: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Evaluate Transaction: RecordKeyExists (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); - const resultBytes = await contract.evaluateTransaction('RecordKeyExists', patientId, recordId); - return this.utf8Decoder.decode(resultBytes) === 'true'; + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: RecordKeyExists (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + const resultBytes = await contract.evaluateTransaction('RecordKeyExists', patientId, recordId); + return this.utf8Decoder.decode(resultBytes) === 'true'; + } catch (error) { + console.error(`RecordKeyExists error for clinic ${clinicId}, falling back to BackupService:`, error); + return backupService.recordKeyExists(patientId, recordId); + } } public async getAllRecords(clinicId: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Evaluate Transaction: GetAllRecords (clinic: ${clinicId})`); - const resultBytes = await contract.evaluateTransaction('GetAllRecords'); - const resultJson = this.utf8Decoder.decode(resultBytes); - return JSON.parse(resultJson) as MedicalRecord[]; + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetAllRecords (clinic: ${clinicId})`); + const resultBytes = await contract.evaluateTransaction('GetAllRecords'); + const resultJson = this.utf8Decoder.decode(resultBytes); + return JSON.parse(resultJson) as MedicalRecord[]; + } catch (error) { + console.error(`GetAllRecords failed for clinic ${clinicId}, falling back to BackupService:`, error); + return backupService.getAllRecords(); + } } public async addRecord(clinicId: string, payload: MedicalRecord): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Submit Transaction: AddRecord (clinic: ${clinicId})`); + try { + const identity = await identityStorage.getIdentity(clinicId); + backupService.addRecord(payload, identity.mspId); + } catch (e) { + console.error(`BackupService addRecord error:`, e); + } - await contract.submit('AddRecord', { - arguments: [payload.patientId, payload.recordId, payload.doctorId, payload.type], - transientData: { - ipfsCid: Buffer.from(payload.ipfsCidKey), - }, - }); + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: AddRecord (clinic: ${clinicId})`); + + await contract.submit('AddRecord', { + arguments: [payload.patientId, payload.recordId, payload.doctorId, payload.type], + transientData: { + ipfsCid: Buffer.from(payload.ipfsCidKey), + }, + }); + } catch (error) { + console.error(`AddRecord transaction failed for clinic ${clinicId}:`, error); + } } public async getRecordsByPatient(clinicId: string, patientId: string, retry = true): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Evaluate Transaction: GetRecordsByPatient (clinic: ${clinicId}, patient: ${patientId})`); - try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Evaluate Transaction: GetRecordsByPatient (clinic: ${clinicId}, patient: ${patientId})`); + const resultBytes = await contract.evaluateTransaction('GetRecordsByPatient', patientId); const resultJson = this.utf8Decoder.decode(resultBytes); return JSON.parse(resultJson) as MedicalRecord[]; } catch (err: unknown) { const msg = (err instanceof Error ? err.message : String(err)) || ''; - console.error(`❌ GetRecordsByPatient error for clinic ${clinicId}:`, err); + console.error(`GetRecordsByPatient error for clinic ${clinicId}:`, err); + // Fallback to BackupService if not an explicit access denial if (msg.toLowerCase().includes('not authorized')) { throw new HttpException(403, `Access denied for clinic ${clinicId} to records of patient ${patientId}`, msg); } // ABORTED (gRPC code 10) usually means the channel is stale — evict and retry once if (retry && (msg.includes('ABORTED') || msg.includes('10 ABORTED'))) { - console.warn(`⚠️ ABORTED on GetRecordsByPatient for clinic ${clinicId}, retrying with fresh connection...`); - await this.getGatewayConnection(clinicId, true); - return this.getRecordsByPatient(clinicId, patientId, false); + console.warn(`ABORTED on GetRecordsByPatient for clinic ${clinicId}, retrying with fresh connection...`); + try { + await this.getGatewayConnection(clinicId, true); + return await this.getRecordsByPatient(clinicId, patientId, false); + } catch (retryErr) { + console.error(`Retry failed, falling back to BackupService for patient ${patientId}`); + try { + const identity = await identityStorage.getIdentity(clinicId); + return backupService.getRecordsByPatient(patientId, identity.mspId); + } catch (e) { + return []; + } + } + } + + console.warn(`Falling back to BackupService for GetRecordsByPatient (patient: ${patientId})`); + try { + const identity = await identityStorage.getIdentity(clinicId); + return backupService.getRecordsByPatient(patientId, identity.mspId); + } catch (e) { + return []; } - throw err; } } public async grantAccess(clinicId: string, patientId: string, targetClinic: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Submit Transaction: GrantAccess (clinic: ${clinicId}, patient: ${patientId})`); const targetMsp = (await identityStorage.getIdentity(targetClinic)).mspId; - await contract.submitTransaction('GrantAccess', patientId, targetMsp); + + try { + const clientIdentity = await identityStorage.getIdentity(clinicId); + backupService.grantAccess(patientId, clientIdentity.mspId, targetMsp); + } catch (e) { + console.error(`BackupService grantAccess error:`, e); + } + + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: GrantAccess (clinic: ${clinicId}, patient: ${patientId})`); + await contract.submitTransaction('GrantAccess', patientId, targetMsp); + } catch (error) { + console.error(`GrantAccess transaction failed for clinic ${clinicId}:`, error); + } } public async updateRecord(clinicId: string, patientId: string, payload: Omit): Promise { - const { contract, identity } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Submit Transaction: UpdateRecord (clinic: ${clinicId})`); + backupService.updateRecord(patientId, payload); - const transientData: Record = {}; - if (payload.ipfsCidKey) { - transientData.ipfsCid = Buffer.from(payload.ipfsCidKey); - } + try { + const { contract, identity } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: UpdateRecord (clinic: ${clinicId})`); - await contract.submit('UpdateRecord', { - arguments: [patientId, payload.recordId, payload.doctorId, payload.type], - ...(Object.keys(transientData).length > 0 ? { transientData } : {}), - }); + const transientData: Record = {}; + if (payload.ipfsCidKey) { + transientData.ipfsCid = Buffer.from(payload.ipfsCidKey); + } + + await contract.submit('UpdateRecord', { + arguments: [patientId, payload.recordId, payload.doctorId, payload.type], + ...(Object.keys(transientData).length > 0 ? { transientData } : {}), + }); + } catch (error) { + console.error(`UpdateRecord transaction failed for clinic ${clinicId}:`, error); + } } public async deleteRecord(clinicId: string, patientId: string, recordId: string): Promise { - const { contract } = await this.getGatewayConnection(clinicId); - console.log(`\n--> Submit Transaction: DeleteRecord (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); - await contract.submitTransaction('DeleteRecord', patientId, recordId); + backupService.deleteRecord(patientId, recordId); + + try { + const { contract } = await this.getGatewayConnection(clinicId); + console.log(`\n--> Submit Transaction: DeleteRecord (clinic: ${clinicId}, patient: ${patientId}, record: ${recordId})`); + await contract.submitTransaction('DeleteRecord', patientId, recordId); + } catch (error) { + console.error(`DeleteRecord transaction failed for clinic ${clinicId}:`, error); + } } public async closeConnection(clinicId: string): Promise { diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index 7a69754..f602bf2 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -12,6 +12,7 @@ import { randomUUID } from 'crypto'; import FabricService from '@/services/fabric.service'; import { RecordType } from '@/interfaces/enums.interface'; import { IdentityStorageService } from '@/services/identity-storage.service'; +import { backupService } from '@/services/backup.service'; @Service() export class MedicalRecordService { @@ -426,6 +427,13 @@ export class MedicalRecordService { }, }); + await this.fabricService.updateRecord(record.clinic_id, patientId, { + recordId, + doctorId: patientId, + type: RecordType.MEDICAL_HISTORY, + ipfsCidKey: newCid, + }); + try { await this.ipfsService.deleteFile(oldCid); } catch (e) { @@ -515,6 +523,8 @@ export class MedicalRecordService { select: { id: true, patient_id: true, clinic_id: true, cid: true }, }); + backupService.deleteAllRecords(); + for (const record of records) { try { await this.fabricService.deleteRecord(record.clinic_id, record.patient_id, record.id); diff --git a/src/swagger-output.json b/src/swagger-output.json index 6a7ac05..d90a19f 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -1093,78 +1093,6 @@ } } }, - "/records": { - "get": { - "tags": [ - "MedicalRecords" - ], - "description": "", - "parameters": [ - { - "name": "X-Fabric-Identity", - "in": "header", - "description": "Identity label (e.g., org1)", - "required": true, - "type": "string" - } - ], - "responses": { - "default": { - "description": "" - } - } - }, - "post": { - "tags": [ - "MedicalRecords" - ], - "description": "Add a new medical record for a patient", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "patientId": { - "type": "string", - "example": "patient-uuid" - }, - "recordId": { - "type": "string", - "example": "record-uuid" - }, - "doctorId": { - "type": "string", - "example": "doctor-uuid" - }, - "type": { - "type": "string", - "example": "LAB_RESULT" - }, - "ipfsCidKey": { - "type": "string", - "example": "bafybeigdyrzt..." - } - }, - "required": [ - "patientId", - "recordId", - "doctorId", - "type", - "ipfsCidKey" - ] - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, "/records/health": { "get": { "tags": [ @@ -1178,123 +1106,6 @@ } } }, - "/records/{patientId}": { - "get": { - "tags": [ - "MedicalRecords" - ], - "description": "Get all records for a patient (authorized MSPs only)", - "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string" - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/records/{patientId}/{recordId}": { - "put": { - "tags": [ - "MedicalRecords" - ], - "description": "Update an existing medical record (doctorId, type, optional new ipfsCidKey via transient)", - "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "recordId", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "recordId": { - "type": "string", - "example": "uuid-record-id" - }, - "doctorId": { - "type": "string", - "example": "doctor-uuid" - }, - "type": { - "type": "string", - "example": "LAB_RESULT" - }, - "ipfsCidKey": { - "type": "string", - "example": "optional-new-cid-key" - } - }, - "required": [ - "recordId", - "doctorId", - "type" - ] - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/records/{patientId}/access": { - "post": { - "tags": [ - "MedicalRecords" - ], - "description": "Grant access to all records of a patient for a target MSP", - "parameters": [ - { - "name": "patientId", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "targetMsp": { - "type": "string", - "example": "Org2MSP" - } - }, - "required": [ - "targetMsp" - ] - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, "/admin/doctors": { "post": { "tags": [ From c1ab4161272f5f2003b3a266b632c607c4e8ba95 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 13 Mar 2026 04:28:40 +0200 Subject: [PATCH 198/210] feat: add fabric identities configuration for multiple clinics --- .gitignore | 6 -- data/fabric-identities.json | 143 ++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 data/fabric-identities.json diff --git a/.gitignore b/.gitignore index 26b2059..b2da325 100644 --- a/.gitignore +++ b/.gitignore @@ -140,12 +140,6 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ -# fabric files -fabric-identities.json - -# fabric files -fabric-identities.json - # Temporary folders docker-compose-local.yml docs diff --git a/data/fabric-identities.json b/data/fabric-identities.json new file mode 100644 index 0000000..7610848 --- /dev/null +++ b/data/fabric-identities.json @@ -0,0 +1,143 @@ +[ + { + "clinicId": "default-clinic", + "mspId": "Org1MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nMIICKDCCAc+gAwIBAgIQB/y0JTTeIX9YJTG4B32NKzAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yNjAzMTIyMTA5MDBaFw0zNjAzMDkyMTA5MDBa\nMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMQ4wDAYDVQQLEwVhZG1pbjEfMB0GA1UEAwwWQWRtaW5Ab3Jn\nMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAlxSOL5PKD3\ngf7sKVjrtdYc3lTmjGP7PF/H1esSeVoRFXZE2Xu9bQkmM1R9MdtBJeOaNBRdvLmw\nR7deaDvfdTajTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud\nIwQkMCKAIAz3e6oT1OeG2VfkhFHKxT8maPufyTbzAnPgsMK7GOruMAoGCCqGSM49\nBAMCA0cAMEQCIA7DcO+EFllKzGA64mMisRdWZ0T6Q326gvXqCJe4sQY9AiAidKZE\nGWDE7RL09PrGI4vWvJb3CXOw9kVOCPJoDbxXcQ==\n-----END CERTIFICATE-----", + "privateKey": "65a4e7218e736d7da865d2287a6a07ab:3b54797ec8eb91a9f041025f75582f19:6263a5005ac5c08cc55722b20dd0718714aa014c22f09cd21e9b8635bce3795d58ba8ccd1b30c9c8bcbf7b860551b2c99ca82de5e0a4f25429a33ad9c7c84ce018c668014c7db192cd152421ae7331e4aca3dfead5cd8b322177c06ba7ad5e9e36b71347ce9d65466d29cd7e536a7b80c33625abfef620c1b9d35272e5fa1e587ee227bba9a41a27c10c2acf5d711d05d09822acd46f8d46425c1c605e964940d699c6c569a1e984ec0bfa5be9c4fa9464392bd42f50abb2b36dfc6205b0a3a761c86df60849dcd9d903fff11dd87bb78dcf11a06cb636dfbc4bd7980fc57b7fd73100dc8ff5a3dcaf9997fd8f4101e3", + "peerEndpoint": "localhost:7051", + "peerHostAlias": "peer0.org1.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nMIICWDCCAf6gAwIBAgIRAIlhgIbqiuuU8Svs2N4e1/wwCgYIKoZIzj0EAwIwdjEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs\nc2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjYwMzEyMjEwOTAwWhcNMzYwMzA5MjEw\nOTAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE\nBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0G\nA1UEAxMWdGxzY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABL2Zr9UiswvsOlBMQ6hV1/IIfZd/1rYPVLDBZUHsCsdILxQvNgvulrXf\nHVl6RAmqyX+ETaLIibVqPvNhv9hGTMijbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV\nHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV\nHQ4EIgQgO+IhPOwblZszvcBX5CIAi/MwT1rIpkoTbC1mN11GF7YwCgYIKoZIzj0E\nAwIDSAAwRQIhAJS/8CifyQA5P4zj9RN22wHZrfjCO/K0QDCVo5nPbLCwAiBJYCAm\n9x5eOopLkz+lc0plVVSjzJkyi/ZnzgZJPmAFng==\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": false, + "createdAt": "2026-03-08T17:44:00.694Z", + "updatedAt": "2026-03-12T21:26:18.651Z" + }, + { + "clinicId": "e77d19d6-6fd9-48c2-94b2-38de12603984", + "mspId": "Org2MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nMIICKTCCAc+gAwIBAgIQKnCVhLWJTJRbYLhKcRwxeTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yNjAzMTIyMTA5MDBaFw0zNjAzMDkyMTA5MDBa\nMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMQ4wDAYDVQQLEwVhZG1pbjEfMB0GA1UEAwwWQWRtaW5Ab3Jn\nMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHaLEL5li9DI\nq7eSNswj42cLGx2SB38AkrtLEwflWTKRXfsLk0hwcl1HZfJ7GhmoTAvsy76sEFt0\ncE7Di6tSEeqjTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud\nIwQkMCKAICeIT9O7Dcy0uoIjis9k41LamWCsU7xZKJXVSs8zAc34MAoGCCqGSM49\nBAMCA0gAMEUCIQDA21YYc/aDEUHs6Ryn3vjzvRo2NkwD0dDj9UI+GAEdfQIgZQFo\nUsSeMvOD6vkHVXyvlxC/F0pBie0uqQSzo5PsT7E=\n-----END CERTIFICATE-----", + "privateKey": "8e90218a084c0dce3c2d1184e8d2ece0:0d15a4c6c131607674a595d43639e2f2:54753626cc14f81280ad3193fbd5fb8f477d94104a324e4e20faf038e4d9fb980f2dc27b91b732368050b3af1b730fcd926cafeca09083d6e76c6253cd4c88e5ec6827d150c465d00641d7876da7cbcbb4402c5650ca1205a715bbbc78c4a57ccfb2e9fb8b6593db832a8f4780c48a1453ca872710bf472a75ad0de19eaea2f3fa088b09f036724bda34c51cc18f7c1c0f12c4738218e9c780d166a02ce6e6b50389732662c54c85e3bb819dc96197d4a9244bf7fc4a6b2216b0d4f2aebdfd37ffab3bffec81bdee7148b56d8f9dea6066d530d4ecc55cb0879cea0f334e81cf13c1b1e0d69a46585f49683cef20066f", + "peerEndpoint": "localhost:9051", + "peerHostAlias": "peer0.org2.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nMIICWDCCAf6gAwIBAgIRAJ50tPpxZYvxeEKTBXxZN/AwCgYIKoZIzj0EAwIwdjEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs\nc2NhLm9yZzIuZXhhbXBsZS5jb20wHhcNMjYwMzEyMjEwOTAwWhcNMzYwMzA5MjEw\nOTAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE\nBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEfMB0G\nA1UEAxMWdGxzY2Eub3JnMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABI7C7U/w5OiekcEintFrDqoNPRJq4rKDAcRgUsl3U7Yxo10xfjSX95Oq\nGyPpqEDJCXzBJhrt5Zs/ho0UiCtppLGjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV\nHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV\nHQ4EIgQgoo7nDFgwWZGFS3XOrh/p1C3FAyVtY1QapkjsLDYJYPswCgYIKoZIzj0E\nAwIDSAAwRQIgJPZJN14gdROzKM8jLOnJEd2lzmxTDMPSFL/n9ASLnzYCIQCM3hWh\nmvN3UdC2g9AvpmEqPtDihNcWVxBTwQb6MpGSag==\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": false, + "createdAt": "2026-03-08T17:44:00.694Z", + "updatedAt": "2026-03-12T21:26:12.849Z" + } + , + { + "clinicId": "dummy-clinic-1", + "mspId": "Org3MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE1\n-----END CERTIFICATE-----", + "privateKey": "dummykey1:dummykey2:dummykey3", + "peerEndpoint": "localhost:11051", + "peerHostAlias": "peer0.org3.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT1\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:00:00.000Z", + "updatedAt": "2026-03-13T10:00:00.000Z" + }, + { + "clinicId": "dummy-clinic-2", + "mspId": "Org4MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE2\n-----END CERTIFICATE-----", + "privateKey": "dummykey4:dummykey5:dummykey6", + "peerEndpoint": "localhost:12051", + "peerHostAlias": "peer0.org4.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT2\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:05:00.000Z", + "updatedAt": "2026-03-13T10:05:00.000Z" + }, + { + "clinicId": "dummy-clinic-3", + "mspId": "Org5MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE3\n-----END CERTIFICATE-----", + "privateKey": "dummykey7:dummykey8:dummykey9", + "peerEndpoint": "localhost:13051", + "peerHostAlias": "peer0.org5.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT3\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:10:00.000Z", + "updatedAt": "2026-03-13T10:10:00.000Z" + }, + { + "clinicId": "dummy-clinic-4", + "mspId": "Org6MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE4\n-----END CERTIFICATE-----", + "privateKey": "dummykey10:dummykey11:dummykey12", + "peerEndpoint": "localhost:14051", + "peerHostAlias": "peer0.org6.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT4\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:15:00.000Z", + "updatedAt": "2026-03-13T10:15:00.000Z" + }, + { + "clinicId": "dummy-clinic-5", + "mspId": "Org7MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE5\n-----END CERTIFICATE-----", + "privateKey": "dummykey13:dummykey14:dummykey15", + "peerEndpoint": "localhost:15051", + "peerHostAlias": "peer0.org7.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT5\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:20:00.000Z", + "updatedAt": "2026-03-13T10:20:00.000Z" + }, + { + "clinicId": "dummy-clinic-6", + "mspId": "Org8MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE6\n-----END CERTIFICATE-----", + "privateKey": "dummykey16:dummykey17:dummykey18", + "peerEndpoint": "localhost:16051", + "peerHostAlias": "peer0.org8.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT6\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:25:00.000Z", + "updatedAt": "2026-03-13T10:25:00.000Z" + }, + { + "clinicId": "dummy-clinic-7", + "mspId": "Org9MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE7\n-----END CERTIFICATE-----", + "privateKey": "dummykey19:dummykey20:dummykey21", + "peerEndpoint": "localhost:17051", + "peerHostAlias": "peer0.org9.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT7\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:30:00.000Z", + "updatedAt": "2026-03-13T10:30:00.000Z" + }, + { + "clinicId": "dummy-clinic-8", + "mspId": "Org10MSP", + "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE8\n-----END CERTIFICATE-----", + "privateKey": "dummykey22:dummykey23:dummykey24", + "peerEndpoint": "localhost:18051", + "peerHostAlias": "peer0.org10.example.com", + "tlsCertificate": "-----BEGIN CERTIFICATE-----\nDUMMYTLSCERT8\n-----END CERTIFICATE-----", + "channelName": "mychannel", + "chaincodeName": "test", + "isDummy": true, + "createdAt": "2026-03-13T10:35:00.000Z", + "updatedAt": "2026-03-13T10:35:00.000Z" + } +] \ No newline at end of file From 4a9846dfe3becee13e361080193d1ab4808a1ef3 Mon Sep 17 00:00:00 2001 From: kareem Date: Sun, 15 Mar 2026 02:05:58 +0200 Subject: [PATCH 199/210] feat: include user ID in appointment response data --- src/controllers/appointment.controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 848af7b..6e1363e 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -537,7 +537,8 @@ export class AppointmentController { ...response, data: { token, - appId: Agora_APP_ID + appId: Agora_APP_ID, + uid: userId } }); }); From 1b591e71e11a31e0c7357dddf1a1e0f09a8904bb Mon Sep 17 00:00:00 2001 From: kareem Date: Thu, 19 Mar 2026 20:45:34 +0200 Subject: [PATCH 200/210] update fabric-identities with neon clinic data --- data/fabric-identities.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/fabric-identities.json b/data/fabric-identities.json index 7610848..f45f8d0 100644 --- a/data/fabric-identities.json +++ b/data/fabric-identities.json @@ -14,7 +14,7 @@ "updatedAt": "2026-03-12T21:26:18.651Z" }, { - "clinicId": "e77d19d6-6fd9-48c2-94b2-38de12603984", + "clinicId": "3928add5-ad4b-4819-ab4c-2bb319836c0c", "mspId": "Org2MSP", "certificate": "-----BEGIN CERTIFICATE-----\nMIICKTCCAc+gAwIBAgIQKnCVhLWJTJRbYLhKcRwxeTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yNjAzMTIyMTA5MDBaFw0zNjAzMDkyMTA5MDBa\nMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMQ4wDAYDVQQLEwVhZG1pbjEfMB0GA1UEAwwWQWRtaW5Ab3Jn\nMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHaLEL5li9DI\nq7eSNswj42cLGx2SB38AkrtLEwflWTKRXfsLk0hwcl1HZfJ7GhmoTAvsy76sEFt0\ncE7Di6tSEeqjTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud\nIwQkMCKAICeIT9O7Dcy0uoIjis9k41LamWCsU7xZKJXVSs8zAc34MAoGCCqGSM49\nBAMCA0gAMEUCIQDA21YYc/aDEUHs6Ryn3vjzvRo2NkwD0dDj9UI+GAEdfQIgZQFo\nUsSeMvOD6vkHVXyvlxC/F0pBie0uqQSzo5PsT7E=\n-----END CERTIFICATE-----", "privateKey": "8e90218a084c0dce3c2d1184e8d2ece0:0d15a4c6c131607674a595d43639e2f2:54753626cc14f81280ad3193fbd5fb8f477d94104a324e4e20faf038e4d9fb980f2dc27b91b732368050b3af1b730fcd926cafeca09083d6e76c6253cd4c88e5ec6827d150c465d00641d7876da7cbcbb4402c5650ca1205a715bbbc78c4a57ccfb2e9fb8b6593db832a8f4780c48a1453ca872710bf472a75ad0de19eaea2f3fa088b09f036724bda34c51cc18f7c1c0f12c4738218e9c780d166a02ce6e6b50389732662c54c85e3bb819dc96197d4a9244bf7fc4a6b2216b0d4f2aebdfd37ffab3bffec81bdee7148b56d8f9dea6066d530d4ecc55cb0879cea0f334e81cf13c1b1e0d69a46585f49683cef20066f", @@ -29,7 +29,7 @@ } , { - "clinicId": "dummy-clinic-1", + "clinicId": "3967d218-5197-4e9d-b833-9bc3e799f8ee", "mspId": "Org3MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE1\n-----END CERTIFICATE-----", "privateKey": "dummykey1:dummykey2:dummykey3", @@ -43,7 +43,7 @@ "updatedAt": "2026-03-13T10:00:00.000Z" }, { - "clinicId": "dummy-clinic-2", + "clinicId": "39af5cd9-7704-4152-b1bd-bc2be7d17891", "mspId": "Org4MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE2\n-----END CERTIFICATE-----", "privateKey": "dummykey4:dummykey5:dummykey6", @@ -57,7 +57,7 @@ "updatedAt": "2026-03-13T10:05:00.000Z" }, { - "clinicId": "dummy-clinic-3", + "clinicId": "499da8a2-f282-417b-b56d-a6395c45c6a3", "mspId": "Org5MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE3\n-----END CERTIFICATE-----", "privateKey": "dummykey7:dummykey8:dummykey9", @@ -71,7 +71,7 @@ "updatedAt": "2026-03-13T10:10:00.000Z" }, { - "clinicId": "dummy-clinic-4", + "clinicId": "54ebf9e3-28e5-4199-91b8-4609a15ef341", "mspId": "Org6MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE4\n-----END CERTIFICATE-----", "privateKey": "dummykey10:dummykey11:dummykey12", @@ -85,7 +85,7 @@ "updatedAt": "2026-03-13T10:15:00.000Z" }, { - "clinicId": "dummy-clinic-5", + "clinicId": "599eb1d4-4d44-40cb-a30a-5a73a13fe8dd", "mspId": "Org7MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE5\n-----END CERTIFICATE-----", "privateKey": "dummykey13:dummykey14:dummykey15", @@ -99,7 +99,7 @@ "updatedAt": "2026-03-13T10:20:00.000Z" }, { - "clinicId": "dummy-clinic-6", + "clinicId": "71f687e2-3fe4-4032-be75-83b753a4a514", "mspId": "Org8MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE6\n-----END CERTIFICATE-----", "privateKey": "dummykey16:dummykey17:dummykey18", @@ -113,7 +113,7 @@ "updatedAt": "2026-03-13T10:25:00.000Z" }, { - "clinicId": "dummy-clinic-7", + "clinicId": "7472ec01-186c-4363-8488-1e0fae6e6929", "mspId": "Org9MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE7\n-----END CERTIFICATE-----", "privateKey": "dummykey19:dummykey20:dummykey21", @@ -127,7 +127,7 @@ "updatedAt": "2026-03-13T10:30:00.000Z" }, { - "clinicId": "dummy-clinic-8", + "clinicId": "b2436b3c-d080-4cd0-a29f-0168dbf41564", "mspId": "Org10MSP", "certificate": "-----BEGIN CERTIFICATE-----\nDUMMYCERTIFICATE8\n-----END CERTIFICATE-----", "privateKey": "dummykey22:dummykey23:dummykey24", From 3e5333383fbb6ae12267ea32fa07f70622fa8a8e Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Fri, 20 Mar 2026 17:43:28 +0200 Subject: [PATCH 201/210] fix: today appointments not found --- src/services/appointment.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 335b2ff..c204c14 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -394,6 +394,7 @@ export class AppointmentService { gte: today, lte: endOfToday, }, + status: { in: ['CONFIRMED', 'COMPLETED'] }, }, select: { id: true, From 92b62e112b5a4d4a64dcc7a584785e6a6d779963 Mon Sep 17 00:00:00 2001 From: salahmohamed03 Date: Sat, 21 Mar 2026 03:49:55 +0200 Subject: [PATCH 202/210] feat: add endpoint to retrieve doctor's appointment context and update request limits --- .gitignore | 3 +- data/backup_keys.json | 3 ++ data/backup_records.json | 3 ++ nginx.conf | 1 + src/app.ts | 2 + src/controllers/appointment.controller.ts | 23 ++++++++++ src/routes/appointment.route.ts | 10 ++++- src/services/appointment.service.ts | 44 +++++++++++++++++++ src/services/identity-storage.service.ts | 52 +++++++++++++++++------ src/services/medical-records.service.ts | 2 +- src/swagger-output.json | 10 +++++ 11 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 data/backup_keys.json create mode 100644 data/backup_records.json diff --git a/.gitignore b/.gitignore index b2da325..4a600bb 100644 --- a/.gitignore +++ b/.gitignore @@ -144,5 +144,4 @@ vite.config.ts.timestamp-* docker-compose-local.yml docs uploads -data/backup_keys.json -data/backup_records.json + diff --git a/data/backup_keys.json b/data/backup_keys.json new file mode 100644 index 0000000..0e0dcd2 --- /dev/null +++ b/data/backup_keys.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/data/backup_records.json b/data/backup_records.json new file mode 100644 index 0000000..c44dc44 --- /dev/null +++ b/data/backup_records.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/nginx.conf b/nginx.conf index 7cb62f6..1450327 100644 --- a/nginx.conf +++ b/nginx.conf @@ -37,4 +37,5 @@ http { sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; + client_max_body_size 5M; } diff --git a/src/app.ts b/src/app.ts index 32cb5d9..7aada57 100644 --- a/src/app.ts +++ b/src/app.ts @@ -75,6 +75,8 @@ export class App { this.app.use(express.urlencoded({ extended: true })); this.app.use(cookieParser()); this.app.use(passport.initialize()); + this.app.use(express.json({ limit: '5mb' })); + this.app.use(express.urlencoded({ limit: '5mb', extended: true })); } diff --git a/src/controllers/appointment.controller.ts b/src/controllers/appointment.controller.ts index 6e1363e..3ebea72 100644 --- a/src/controllers/appointment.controller.ts +++ b/src/controllers/appointment.controller.ts @@ -254,6 +254,29 @@ export class AppointmentController { }); }); + public getDoctorAppointmentContext = catchAsync(async (req: RequestWithUser, res: Response): Promise => { + const doctorId = req.user.id; + const { appointmentId } = req.params; + + if (!doctorId) { + const error = createBilingualError(400, ErrorMessages.DOCTOR_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!appointmentId) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ID_REQUIRED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + const context = await this.appointmentService.getDoctorAppointmentContext(doctorId, appointmentId); + const response = createMultiLangMessage(SuccessResponseMessages.APPOINTMENT_DETAILS_RETRIEVED); + + res.status(200).json({ + data: context, + ...response, + }); + }); + public enterDoctorSchedule = catchAsync(async (req: RequestWithUser, res: Response): Promise => { const doctorId = req.user.id; const { diff --git a/src/routes/appointment.route.ts b/src/routes/appointment.route.ts index 175ab4b..d070ddc 100644 --- a/src/routes/appointment.route.ts +++ b/src/routes/appointment.route.ts @@ -4,9 +4,10 @@ import { ClinicController } from "@/controllers/clinic.controller"; import { DoctorController } from "@/controllers/doctor.controller"; import { AppointmentController } from "@/controllers/appointment.controller"; import { ValidationMiddleware } from "@/middlewares/validation.middleware"; -import { AuthMiddleware } from "@/middlewares/auth.middleware"; +import { AuthMiddleware, RoleMiddleware } from "@/middlewares/auth.middleware"; import { BookAppointmentDto, RescheduleAppointmentDto, RescheduleAppointmentByDoctorDto, EnterDoctorScheduleDto, EditDoctorScheduleDto, HandleDoctorVacationDto } from "@/dtos/appointments.dto"; import { AiAppointmentsRoute } from "./ai_appointments.route"; +import { Role } from "@prisma/client"; export class AppointmentRoute implements Routes { public path = '/appointments'; @@ -983,6 +984,13 @@ export class AppointmentRoute implements Routes { this.appointmentController.getCurrentDoctorSchedule ); + this.router.get( + `${this.path}/doctor/:appointmentId/context`, + AuthMiddleware, + RoleMiddleware(Role.DOCTOR), + this.appointmentController.getDoctorAppointmentContext, + ); + this.router.get( `${this.path}/doctor/daily-schedule`, /* diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index c204c14..8618d8e 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1203,6 +1203,50 @@ export class AppointmentService { } + public async getDoctorAppointmentContext( + doctorId: string, + appointmentId: string, + ): Promise<{ appointmentId: string; clinicId: string; patientId: string }> { + const appointment = await prisma.appointment.findUnique({ + where: { + id: appointmentId, + }, + select: { + id: true, + doctor_id: true, + clinic_id: true, + patient_id: true, + deleted_at: true, + }, + }); + + if (!appointment) { + const error = createBilingualError(404, ErrorMessages.APPOINTMENT_NOT_FOUND); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.doctor_id !== doctorId) { + const error = createBilingualError(403, ErrorMessages.UNAUTHORIZED_APPOINTMENT_ACCESS); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (appointment.deleted_at) { + const error = createBilingualError(400, ErrorMessages.APPOINTMENT_ALREADY_DELETED); + throw new HttpException(error.status, error.message, error.messageAr); + } + + if (!appointment.clinic_id) { + const error = createBilingualError(400, ErrorMessages.CLINIC_REQUIRED_FOR_OFFLINE); + throw new HttpException(error.status, error.message, error.messageAr); + } + + return { + appointmentId: appointment.id, + clinicId: appointment.clinic_id, + patientId: appointment.patient_id, + }; + } + public async getAppointmentOwners(appointmentId: string): Promise<{ doctorId: string; scheduledTime: Date; }> { const appointment = await prisma.appointment.findUnique({ where: { diff --git a/src/services/identity-storage.service.ts b/src/services/identity-storage.service.ts index edd1cae..a880525 100644 --- a/src/services/identity-storage.service.ts +++ b/src/services/identity-storage.service.ts @@ -181,23 +181,49 @@ export class IdentityStorageService { private decrypt(ciphertext: string): string { - if (!ciphertext.includes(':')) { - // Data is not encrypted + if (!this.isEncryptedPayload(ciphertext)) { + // Value is plain text or a non-AES placeholder (e.g. dummy keys) return ciphertext; } const [ivHex, authTagHex, encrypted] = ciphertext.split(':'); - - const iv = Buffer.from(ivHex, 'hex'); - const authTag = Buffer.from(authTagHex, 'hex'); - - const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv); - decipher.setAuthTag(authTag); - - let decrypted = decipher.update(encrypted, 'hex', 'utf8'); - decrypted += decipher.final('utf8'); - - return decrypted; + + try { + const iv = Buffer.from(ivHex, 'hex'); + const authTag = Buffer.from(authTagHex, 'hex'); + + const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + } catch { + // Keep backward compatibility with legacy/plaintext values that happen to contain ':' + return ciphertext; + } + } + + private isEncryptedPayload(value: string): boolean { + const parts = value.split(':'); + if (parts.length !== 3) { + return false; + } + + const [ivHex, authTagHex, encryptedHex] = parts; + const isHex = (input: string) => /^[0-9a-fA-F]+$/.test(input); + + // AES-256-GCM format: 16-byte IV + 16-byte auth tag + hex ciphertext + if (ivHex.length !== 32 || authTagHex.length !== 32) { + return false; + } + + if (encryptedHex.length === 0 || encryptedHex.length % 2 !== 0) { + return false; + } + + return isHex(ivHex) && isHex(authTagHex) && isHex(encryptedHex); } private sanitizeIdentity(identity: FabricIdentity): FabricIdentity { diff --git a/src/services/medical-records.service.ts b/src/services/medical-records.service.ts index f602bf2..2b9c863 100644 --- a/src/services/medical-records.service.ts +++ b/src/services/medical-records.service.ts @@ -472,7 +472,7 @@ export class MedicalRecordService { } public async getVisitSummariesForDoctor(doctorId: string, patientId: string): Promise> { - return this.getJsonRecordsForDoctor(doctorId, patientId, RecordType.VISIT); + return this.getJsonRecordsForDoctor(doctorId, patientId, RecordType.VISIT_SUMMARY); } public async getMedicalHistoryForDoctor(doctorId: string, patientId: string): Promise> { diff --git a/src/swagger-output.json b/src/swagger-output.json index 6e1301f..628414d 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -6852,6 +6852,16 @@ } } }, + "${this.path}/doctor/{appointmentId}/context": { + "get": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, "/appointments/doctor/daily-schedule": { "get": { "tags": [ From 688ba5a0563ce71ab13500d8c73212e44f9eb322 Mon Sep 17 00:00:00 2001 From: kareem Date: Sat, 21 Mar 2026 20:06:10 +0200 Subject: [PATCH 203/210] feat: increase request body size limit in app and nginx configuration --- nginx.conf | 2 ++ src/app.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nginx.conf b/nginx.conf index 7cb62f6..9261593 100644 --- a/nginx.conf +++ b/nginx.conf @@ -37,4 +37,6 @@ http { sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; + client_max_body_size 50M; + } diff --git a/src/app.ts b/src/app.ts index 32cb5d9..43dc290 100644 --- a/src/app.ts +++ b/src/app.ts @@ -71,8 +71,8 @@ export class App { this.app.use(hpp()); this.app.use(helmet()); this.app.use(compression()); - this.app.use(express.json()); - this.app.use(express.urlencoded({ extended: true })); + this.app.use(express.json({ limit: '5mb' })); + this.app.use(express.urlencoded({ limit: '5mb', extended: true })); this.app.use(cookieParser()); this.app.use(passport.initialize()); } From 2b20e4e175237faee33230597eff1bc4782d8762 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Mon, 23 Mar 2026 03:20:02 +0200 Subject: [PATCH 204/210] Fix time zone mismatch in appointments --- src/services/appointment.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 8618d8e..056dee5 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -118,8 +118,9 @@ export class AppointmentService { const now = new Date(); const { start: today, end: endOfToday } = this.getTodayBoundaries(now); - const requestedDateOnly = new Date(requestedDate); - requestedDateOnly.setUTCHours(0, 0, 0, 0); + // const requestedDateOnly = new Date(requestedDate); + // requestedDateOnly.setUTCHours(0, 0, 0, 0); + const { start: requestedDateOnly } = this.getTodayBoundaries(requestedDate); if (requestedDateOnly < today) { const error = createBilingualError(400, ErrorMessages.APPOINTMENT_IN_PAST); From 467cfeb998208edbc758fb12890f2c7642491af0 Mon Sep 17 00:00:00 2001 From: kareem Date: Sun, 19 Apr 2026 12:32:43 +0200 Subject: [PATCH 205/210] Add medical history keys --- data/backup_keys.json | 7 ++++- data/backup_records.json | 55 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/data/backup_keys.json b/data/backup_keys.json index 0e0dcd2..b537e8d 100644 --- a/data/backup_keys.json +++ b/data/backup_keys.json @@ -1,3 +1,8 @@ { - + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:614318c7-6890-44d1-971c-98899f46c3c9": "5d520490ead9510027da3b8100250cbc173c53641f755e61379f642ed45d5eed67b3358d2c10a865ca0e07f1ba9cd38c2b25e3b1bcabba9454228855", + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:f9538b49-2961-4a23-a6ef-e90ecaf46a58": "4bc632a9bf4dc85c7020f08ed93911efdfed0efa43f07119eda138c8299324a367751b485d9651258fbdca8fd429ece8d55b618d707d0e58fb59d957", + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:ef9983d2-4bef-49d6-b449-d29e009f3689": "88b271fffad3787c3affbb0c3be6d27ba2396229eda1119c3f0dc55443e610516a0e9d8945442820c14a942a8a722fffd03495f924a06be95cbdc7e0", + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:031577cd-2961-4504-b069-d206febd3496": "1af3e3c68c7a565ddb11c2fed2fd2b63b7777173ffedc10bb51e3b59066030d47c80119b55eedb1305e7c1e075897dcc649c95fbe2fc78bdbc44500b", + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:9a8cc315-c829-4b48-9cef-eec56b0dd340": "4e7051a9923735d4b4e51e3a21138bef0b375a9ae1a862c79c7a37a863adb895a9f1c22f2ee00db24ca3b4c4c4e3c211dd28f200ab934ee2dcf13316", + "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa:454f85de-5f53-4a39-8ecb-62f6add946c2": "bbf13ab3175b3169ffd292822249ce0dedc92cdab0d8fb7bf37d2e0ce872cee73c308bc9de1858a167bd48b5efb0a1e4bf49ae5dd65ca51cc81aa7e4" } \ No newline at end of file diff --git a/data/backup_records.json b/data/backup_records.json index c44dc44..df04a3c 100644 --- a/data/backup_records.json +++ b/data/backup_records.json @@ -1,3 +1,56 @@ [ - + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "614318c7-6890-44d1-971c-98899f46c3c9", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafkreiepvqgyjgffz6rqx63p5fgib5cu2lj2iq2kylhrzqsebkmjluhi5u", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + }, + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "f9538b49-2961-4a23-a6ef-e90ecaf46a58", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafkreiexyo5c6hxzko3llm5ucgzn4ykudlk6rcrt3jqfy5knsqclwcynn4", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + }, + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "ef9983d2-4bef-49d6-b449-d29e009f3689", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafybeigrcegro2w4fdoiovgzz7pjndemw2rhrc4buayhaca2wjxu5y574y", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + }, + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "031577cd-2961-4504-b069-d206febd3496", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafybeicon54ajpytn557hvkup33adsn5un3iprsa3kvyg446573pfnzk5a", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + }, + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "9a8cc315-c829-4b48-9cef-eec56b0dd340", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafkreifvjkay6n2yzi5hm4mf6pouo2ffxrbrpktde2j4vtjzl3suuiiddu", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + }, + { + "patientId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "recordId": "454f85de-5f53-4a39-8ecb-62f6add946c2", + "doctorId": "e8aa15ca-fc0a-43cf-8490-bd3b0be653fa", + "type": "MEDICAL_HISTORY", + "ipfsCidKey": "bafkreiehe3rnz5l6p2dfdhceatf6odbbyorzmz7qbeqim2l7auj3z7auey", + "ownerMsp": "Org1MSP", + "authorizedMsps": [] + } ] \ No newline at end of file From 2460c0bbcf54784eb5b6e88da1da26c5d1bca12f Mon Sep 17 00:00:00 2001 From: Youssef-Abo-El-Ela Date: Fri, 24 Apr 2026 19:44:24 +0300 Subject: [PATCH 206/210] Added prompt param to process ai endpoint --- .gitignore | 1 + .vscode/settings.json | 3 ++- src/controllers/ai_appointments.controller.ts | 4 ++-- src/routes/ai_appointments.route.ts | 3 ++- src/services/ai_appointments.service.ts | 6 +++--- src/swagger-output.json | 21 ++++++++++++++++++- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4a600bb..5a05c1f 100644 --- a/.gitignore +++ b/.gitignore @@ -144,4 +144,5 @@ vite.config.ts.timestamp-* docker-compose-local.yml docs uploads +backblaze_cors.json diff --git a/.vscode/settings.json b/.vscode/settings.json index c5e5d42..4268241 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,6 @@ "editor.formatOnSave": false, "yaml.schemas": { "https://www.schemastore.org/github-workflow.json": "file:///home/enjy/work/GP/repo/Backend/.github/workflows/ci.yml" - } + }, + "js/ts.tsdk.path": "node_modules\\typescript\\lib" } diff --git a/src/controllers/ai_appointments.controller.ts b/src/controllers/ai_appointments.controller.ts index 7f27cb0..5d07712 100644 --- a/src/controllers/ai_appointments.controller.ts +++ b/src/controllers/ai_appointments.controller.ts @@ -38,7 +38,7 @@ export class AiAppointmentsController { public processAudioAI = catchAsync(async (req: RequestWithUser, res: Response, next: NextFunction): Promise => { const appointmentId = req.params.appointmentId; - const { doctorKey, patientKey, mixedKey } = req.body; + const { doctorKey, patientKey, mixedKey, prompt } = req.body; const isAppointmentExist = await this.aiAppointmentsService.checkAppointmentExistence(appointmentId); if (!isAppointmentExist) { @@ -60,7 +60,7 @@ export class AiAppointmentsController { const error = createBilingualError(400, ErrorMessages.MISSING_AUDIO_KEYS); throw new HttpException(error.status, error.message, error.messageAr); } - const SOAP = await this.aiAppointmentsService.generateSOAP(finalScript); + const SOAP = await this.aiAppointmentsService.generateSOAP(finalScript, prompt); const responseMessage = createMultiLangMessage(SuccessResponseMessages.SOAP_GENERATED); res.status(202).json({ ...responseMessage, diff --git a/src/routes/ai_appointments.route.ts b/src/routes/ai_appointments.route.ts index 78cd108..491a117 100644 --- a/src/routes/ai_appointments.route.ts +++ b/src/routes/ai_appointments.route.ts @@ -88,7 +88,8 @@ export class AiAppointmentsRoute implements Routes { schema: { $doctorKey: 'appointments/appointmentId/DOCTOR.webm', $patientKey: 'appointments/appointmentId/PATIENT.webm', - $mixedKey: 'appointments/appointmentId/MIXED.webm' + $mixedKey: 'appointments/appointmentId/MIXED.webm', + $prompt: 'string' } } #swagger.responses[202] = { diff --git a/src/services/ai_appointments.service.ts b/src/services/ai_appointments.service.ts index 211374b..f53257b 100644 --- a/src/services/ai_appointments.service.ts +++ b/src/services/ai_appointments.service.ts @@ -57,12 +57,12 @@ export class AiAppointmentsService { return finalScript; } - public async generateSOAP(finalScript: string): Promise { + public async generateSOAP(finalScript: string, prompt: string): Promise { const chatCompletion = await this.groq.chat.completions.create({ messages: [ { role: "system", - content: `You are an expert clinical AI scribe specializing in rheumatology and autoimmune diseases. + content: prompt ? prompt : `You are an expert clinical AI scribe specializing in rheumatology and autoimmune diseases. Your task is to analyze the provided doctor-patient consultation transcript and generate a highly professional, concise medical SOAP note. CRITICAL INSTRUCTIONS: @@ -71,7 +71,7 @@ CRITICAL INSTRUCTIONS: 3. The input transcript may contain Egyptian Arabic, English, or a mix of both. You MUST translate all clinical findings into standard professional medical English. CLINICAL GUIDELINES: -- Subjective: Focus on the chief complaint, history of present illness, pain levels, and specific autoimmune symptoms (e.g., duration of morning stiffness, fatigue). +- Subjective: Focus on the chief complaint, history of present illness, family medical history, pain levels, and specific autoimmune symptoms (e.g., duration of morning stiffness, fatigue). - Objective: Extract any physical examination findings mentioned by the doctor (e.g., synovitis, swollen MCP/PIP joints, range of motion) and any lab/imaging results discussed. - Assessment: State the suspected or confirmed diagnosis (e.g., Rheumatoid Arthritis flare, SLE) based on the context. - Plan: List the treatment strategy clearly, including medication changes (e.g., Methotrexate, NSAIDs, Biologics), ordered labs (e.g., CRP, ESR, Anti-CCP), and follow-up instructions.` diff --git a/src/swagger-output.json b/src/swagger-output.json index 628414d..7b9f5cf 100644 --- a/src/swagger-output.json +++ b/src/swagger-output.json @@ -6855,6 +6855,20 @@ "${this.path}/doctor/{appointmentId}/context": { "get": { "description": "", + "parameters": [ + { + "name": "this.path", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "appointmentId", + "in": "path", + "required": true, + "type": "string" + } + ], "responses": { "default": { "description": "" @@ -9352,12 +9366,17 @@ "mixedKey": { "type": "string", "example": "appointments/appointmentId/MIXED.webm" + }, + "prompt": { + "type": "string", + "example": "string" } }, "required": [ "doctorKey", "patientKey", - "mixedKey" + "mixedKey", + "prompt" ] } } From 2280cc2bd2c93ded020259e4bb55ea82c368801c Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 26 Apr 2026 17:33:51 +0300 Subject: [PATCH 207/210] fix: TIMING in today appointments --- src/services/appointment.service.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 056dee5..4d7cda9 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1793,15 +1793,16 @@ export class AppointmentService { } private getTodayBoundaries(date: Date, timezone: string = 'Africa/Cairo'): { start: Date; end: Date } { - const localDateStr = new Intl.DateTimeFormat('en-CA', { timeZone: timezone, - year: 'numeric', month: '2-digit', day: '2-digit' - }).format(date); + year: 'numeric', + month: '2-digit', + day: '2-digit'}).format(date); - const start = new Date(`${localDateStr}T00:00:00+02:00`); - const end = new Date(`${localDateStr}T23:59:59.999+02:00`); + const [year, month, day] = localDateStr.split('-').map(Number); + const start = new Date(Date.UTC(year, month - 1, day, 0, 0, 0, 0)); + const end = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999)); return { start, end }; } From 612af7ad5a44a17cf4c30f54ae21971ec312798f Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Sun, 26 Apr 2026 21:13:50 +0300 Subject: [PATCH 208/210] remove unnecessary stuff --- src/services/appointment.service.ts | 89 ----------------------------- 1 file changed, 89 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 4d7cda9..66e1c5b 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1639,19 +1639,6 @@ export class AppointmentService { return (slotStart < appointmentEnd && slotEnd > appointmentStart); } - private async doctorIsOnline(doctorId: string): Promise { - const { availability_type } = await prisma.doctor.findUnique({ - where: { - id: doctorId, - }, - select: { - availability_type: true, - } - }); - return availability_type === 'ONLINE' || availability_type === 'BOTH'; - } - - private async rescheduleSingleAppointment(doctorId: string, appointmentId: string, minutes: number): Promise { const appointment = await this.getAndValidateAppointment(appointmentId, doctorId); @@ -1712,82 +1699,6 @@ export class AppointmentService { return appointment; } - private async validateDoctorAvailability(doctorId: string, clinicId: string | null, newScheduledTime: Date, newEndTime: Date, excludeAppointmentId?: string): Promise { - - // check if doctor works on this day - const dayOfWeek = this.getDayOfWeek(newScheduledTime.getUTCDay()); - const isOnline = await this.doctorIsOnline(doctorId); - - const schedule = await prisma.doctorSchedule.findFirst({ - where: { - doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, - day_of_week: dayOfWeek, - is_active: true, - deleted_at: null, - }, - select: { - start_time: true, - end_time: true, - } - }); - - if (!schedule) { - const error = createBilingualError(400, ErrorMessages.DOCTOR_NOT_WORKING_ON_DAY); - throw new HttpException(error.status, error.message, error.messageAr); - } - - // check if the new time within schedule or not - const scheduleStart = this.parseTimeToDate(newScheduledTime, schedule.start_time); - const scheduleEnd = this.parseTimeToDate(newScheduledTime, schedule.end_time); - - if (newScheduledTime < scheduleStart || newEndTime > scheduleEnd) { - const error = createBilingualError(400, ErrorMessages.TIME_OUTSIDE_SCHEDULE); - throw new HttpException(error.status, error.message, error.messageAr); - } - - // check for any conflicts with existing appointments (appointments on the same calendar day) - const startOfDay = new Date(newScheduledTime); - startOfDay.setUTCHours(0, 0, 0, 0); - - const endOfDay = new Date(newScheduledTime); - endOfDay.setUTCHours(23, 59, 59, 999); - - const whereClause: any = { - doctor_id: doctorId, - clinic_id: isOnline ? null : clinicId, - scheduled_time: { - gte: startOfDay, - lte: endOfDay, - }, - status: { in: ['CONFIRMED', 'COMPLETED'] }, - deleted_at: null, - }; - - // exclude the appointment being rescheduled - if (excludeAppointmentId) { - whereClause.id = { not: excludeAppointmentId }; - } - - const conflictingAppointments = await prisma.appointment.findMany({ - where: whereClause, - select: { - scheduled_time: true, - end_time: true, - } - }); - - // check for overlap - const hasConflict = conflictingAppointments.some(existing => { - return this.doesSlotOverlap(newScheduledTime, newEndTime, new Date(existing.scheduled_time), new Date(existing.end_time)); - }); - - if (hasConflict) { - const error = createBilingualError(400, ErrorMessages.TIME_SLOT_NOT_AVAILABLE); - throw new HttpException(error.status, error.message, error.messageAr); - } - } - private camelToSnakeCase(str: string): string { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } From f1cc1f721fcb698c072f7e85406284ea85bb12f2 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Thu, 30 Apr 2026 22:45:16 +0300 Subject: [PATCH 209/210] centralize all time logic --- src/services/appointment.service.ts | 64 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 66e1c5b..464e90a 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -56,8 +56,8 @@ export class AppointmentService { scheduleMap.set(schedule.day_of_week, schedule); }); - const today = new Date(); - today.setUTCHours(0, 0, 0, 0); + const now = this.getNowInEgypt(); + const today = this.getTodayBoundaries(now).start; for (let i = 0; i < daysAhead; i++) { // create a copy from today --> if we used today directly it will be modified to today + 1 --> tomorrow date @@ -115,11 +115,8 @@ export class AppointmentService { const requestedDate = new Date(date); const dayOfWeek = this.getDayOfWeek(requestedDate.getUTCDay()); - const now = new Date(); - const { start: today, end: endOfToday } = this.getTodayBoundaries(now); - - // const requestedDateOnly = new Date(requestedDate); - // requestedDateOnly.setUTCHours(0, 0, 0, 0); + const now = this.getNowInEgypt(); + const { start: today } = this.getTodayBoundaries(now); const { start: requestedDateOnly } = this.getTodayBoundaries(requestedDate); if (requestedDateOnly < today) { @@ -199,9 +196,6 @@ export class AppointmentService { return this.doesSlotOverlap(slotStart, slotEnd, apptStart, apptEnd); }); - const nowUTC = new Date(); - const egyptOffset = 2 * 60 * 60 * 1000; - const now = new Date(nowUTC.getTime() + egyptOffset); const isInPast = slotStart <= now; return { @@ -385,7 +379,7 @@ export class AppointmentService { public async getTodayAppointment(patientId: string): Promise { const result: PatientTodayAppointment[] = []; - const now = new Date(); + const now = this.getNowInEgypt(); const { start: today, end: endOfToday } = this.getTodayBoundaries(now); const appointments = await prisma.appointment.findMany({ @@ -507,8 +501,6 @@ export class AppointmentService { appointmentDate: this.formatDate(appointment.scheduled_time), startTime: this.formatTime(appointment.scheduled_time), }; - - // penalty to be added later } public async rescheduleAppointmentByDoctor(doctorId: string, appointmentId: string, minutes: number): Promise { @@ -746,10 +738,7 @@ export class AppointmentService { }; public async getUpcommingDoctorSchedule(doctorId: string): Promise { - // to be changed later --> - const nowUTC = new Date(); - const egyptOffset = 2 * 60 * 60 * 1000; - const now = new Date(nowUTC.getTime() + egyptOffset); + const now = this.getNowInEgypt(); const appointments = await prisma.appointment.findMany({ where: { @@ -966,10 +955,7 @@ export class AppointmentService { throw new HttpException(error.status, error.message, error.messageAr); } - const nowUTC = new Date(); - const egyptOffset = 2 * 60 * 60 * 1000; - const now = new Date(nowUTC.getTime() + egyptOffset); - + const now = this.getNowInEgypt(); if (now < appointment.scheduled_time) { const error = createBilingualError(400, ErrorMessages.CANNOT_BE_COMPLETED_BEFORE_SCHEDULED_TIME); @@ -1155,7 +1141,7 @@ export class AppointmentService { } public async getCurrentDoctorSchedule(doctorId: string): Promise { - const now = new Date(); + const now = this.getNowInEgypt(); const { start: startOfDay, end: endOfDay } = this.getTodayBoundaries(now); const appointments = await prisma.appointment.findMany({ @@ -1382,7 +1368,6 @@ export class AppointmentService { } }) - // DONT FORGET LATER --> notify patients/ penalty } @@ -1411,12 +1396,10 @@ export class AppointmentService { deleted_at: new Date(), } }) - // DONT FORGET LATER --> notify patients / penalty - } public async getNurseAppointmentsToday(nurseId: string): Promise { - const crrentDate = new Date(); + const crrentDate = this.getNowInEgypt(); const today = this.formatDate(crrentDate); const dayOfWeek = this.getDayOfWeek(crrentDate.getUTCDay()); @@ -1703,20 +1686,43 @@ export class AppointmentService { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } - private getTodayBoundaries(date: Date, timezone: string = 'Africa/Cairo'): { start: Date; end: Date } { - const localDateStr = new Intl.DateTimeFormat('en-CA', { + private getNowInEgypt(): Date { + const now = new Date(); + + const cairoFormatter = new Intl.DateTimeFormat('en-CA', { + timeZone: 'Africa/Cairo', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }); + + const cairoDateTimeStr = cairoFormatter.format(now).replace(' ', 'T'); + return new Date(`${cairoDateTimeStr}Z`); + } + + private getTodayBoundaries(date: Date = new Date(), timezone: string = 'Africa/Cairo'): { start: Date; end: Date } { + const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: timezone, year: 'numeric', month: '2-digit', - day: '2-digit'}).format(date); + day: '2-digit' + }); + const localDateStr = formatter.format(date); const [year, month, day] = localDateStr.split('-').map(Number); const start = new Date(Date.UTC(year, month - 1, day, 0, 0, 0, 0)); const end = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999)); + return { start, end }; } + + public async generateAgoraToken(appointmentId: string, userId: string): Promise { const appointment = await prisma.appointment.findUnique({ where: { From 27eac4baa1b1ee1c415df9a558d0c6a659c51c04 Mon Sep 17 00:00:00 2001 From: enjyashraf18 Date: Fri, 1 May 2026 23:25:31 +0300 Subject: [PATCH 210/210] fix bug: boundaries of the day --- src/services/appointment.service.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/services/appointment.service.ts b/src/services/appointment.service.ts index 464e90a..67cd59c 100644 --- a/src/services/appointment.service.ts +++ b/src/services/appointment.service.ts @@ -1700,29 +1700,22 @@ export class AppointmentService { hour12: false, }); - const cairoDateTimeStr = cairoFormatter.format(now).replace(' ', 'T'); + const cairoDateTimeStr = cairoFormatter.format(now).replace(', ', 'T'); return new Date(`${cairoDateTimeStr}Z`); } - private getTodayBoundaries(date: Date = new Date(), timezone: string = 'Africa/Cairo'): { start: Date; end: Date } { - const formatter = new Intl.DateTimeFormat('en-CA', { - timeZone: timezone, - year: 'numeric', - month: '2-digit', - day: '2-digit' - }); - - const localDateStr = formatter.format(date); - const [year, month, day] = localDateStr.split('-').map(Number); + private getTodayBoundaries(date: Date): { start: Date; end: Date } { + const year = date.getUTCFullYear(); + const month = date.getUTCMonth(); + const day = date.getUTCDate(); - const start = new Date(Date.UTC(year, month - 1, day, 0, 0, 0, 0)); - const end = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999)); + const start = new Date(Date.UTC(year, month, day, 0, 0, 0, 0)); + const end = new Date(Date.UTC(year, month, day, 23, 59, 59, 999)); return { start, end }; } - public async generateAgoraToken(appointmentId: string, userId: string): Promise { const appointment = await prisma.appointment.findUnique({ where: {